前面我们已经对四大组件的三个有了一定程度的了解,接下来我们来了解四大组件的最后一个——ContentProvider(内容提供者)。相比较前面的三个,内容提供者在日常的开发过程中用到的比较少,但是这并不意味着它不重要,所以接下来我们就来一起来揭开它神秘的面纱吧。
我们知道在Android中,大部分的数据都是在应用内自己运作的,即应用的数据大部分都是私有的,那么,假设我需要将自己应用内的数据暴露给其他的APP或者说我需要获取系统上的其他应用的数据该怎么办呢?这个时候ContentProvider就派上用场了。
ContentProvider的主要作用就是解决应用之间数据共享的问题,包括获取系统内置的APP(比如联系人)和那些暴露接口的APP,那么ContentProvider到底是怎么运作的呢?在讲解ContentProvider之前,我们先来了解一下几个比较重要的概念。
概念一:URI(统一资源标识符)
我们通常在获取后端数据的时候,都会传递一个URL地址,通常的写法是
然后在此基础上传递参数,通过这样的方式就能拿到我们想要的数据,大家难道都不会好奇为什么就靠这样一行简单的代码就可以拿到我们想要的数据啦,其实大家了解网络这一块的话就会知道,网络上的所有东西,包括网页、文本、图片、音频、视频其实都是有一个地址的,类似于我们的身份证,具有唯一性,每当我们发出一个这样的请求时,系统就会根据我们给的地址去找到对应的资源文件并进行返回,由于地址具有唯一性,所以我们可以正确的拿到准确的数据。那么对于那些手机上的那些资源文件,我们又怎么拿到想要的数据呢?这个时候URI就能派上用场了。那么什么是URI呢?它和URL又有什么区别呢?
URI包括URL和URN两个类别,URL是URI的子集,所以URL一定是URI,而URI不一定是URL。
URI = Uniform Resource Identifier 统一资源标志符,用来标识抽象或物理资源的一个紧凑字符串。
URL = Uniform Resource Locator 统一资源定位符,一种定位资源的主要访问机制的字符串,一个标准的URL必须包括:protocol、host、port、path、parameter、anchor。
URN = Uniform Resource Name 统一资源名称,通过特定命名空间中的唯一名称或ID来标识资源。
当然在Android开发中,我们经常使用的并不是URI而是Uri,那么这个Uri又是什么呢?它和URI有什么区别呢?
· 1.所属的包不同。URI位置在java.net.URI,显然是Java提供的一个类。而Uri位置在android.net.Uri,是由Android提供的一个类。所以初步可以判断,Uri是URI的“扩展”以适应Android系统的需要。
· 2.作用的不同。URI类代表了一个URI(这个URI不是类,而是其本来的意义:通用资源标志符——Uniform Resource Identifier)实例。Uri类是一个不可改变的URI引用,包括一个URI和一些碎片,URI跟在“#”后面。建立并且转换URI引用。而且Uri类对无效的行为不敏感,对于无效的输入没有定义相应的行为,如果没有另外制定,它将返回垃圾而不是抛出一个异常。
简单来说,Uri是Android开发的,扩展了Java中URI的一些功能来特定的适用于Android开发,所以大家在开发时,只使用Android 提供的Uri即可。那么我们来看看Uri的组成结构是什么样子的。
Uri结构:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
Scheme:Uri的模式,比如http、file、content。如果Uri中没有指定scheme,意味着Uri无效。
Host:Uri的主机名,比如www.baidu.com。如果Uri中没有指定host,意味着Uri无效。
Port:Uri的端口号,比如8080,仅在scheme和host指定的情况下才有意义。
Path、pathPrefix、pathPattern表示路径信息。Path对应的是完整的路径信息;pathPattern也表示的是完整的路径信息,但是允许其中包含通配符。pathPrefix表示路径的前缀信息。
概念二:UriMatch
主要用于匹配Uri.这里的匹配是发生在ContentProvider中的,假如我们向ContentProvider中插入一条数据,不可能为所欲为的想怎么干就怎么干,在ContentProvider肯定要做一个判断,只有在符合条件下才会去执行你想要执行的操作,这里的判断就是用UriMatch进行匹配。主要的方法有两个:
(1)UriMatch.addURI(String authority, String path, int code)
在ContentProvider添加一个用于匹配的Uri,当匹配成功时返回code。Uri可以是精确的字符串,Uri中带有*表示可匹配任意text,#表示只能匹配数字。
(2)UriMatch.match(Uri uri)
对传递过来的uri进行验证,查看是否匹配成功。
概念三:ContentResolver
主要用于对ContentProvider中的数据进行增删改查的操作,其主要的方法有:
1)public Uri insert(Uri uri, ContentValues values)
该方法用于往ContentProvider添加数据。
2)public int delete(Uri uri, String selection, String[] selectionArgs)
该方法用于从ContentProvider删除数据。
3)public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
该方法用于更新ContentProvider中的数据。
4)public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
该方法用于从ContentProvider中获取数据。
这四个方法中每个方法的参数所代表的意思是:
(1)Uri uri:指定查询某个应用程序下的某一张表(注意,ContentProvider对外提供的数据更多是以数据库表的形式展现的)
(2)String[] projection:指定查询的列名
(3)ContentValues values:SQLite数据库操作的辅助类
(4)String selection:指定where的约束条件
(5)String[] selectionArgs:为where中的占位符提供具体的值
(6)String sortOrder:指定查询结果的排序方式
概念四:ContentProvider中getType()方法
getType()是所有的内容提供者都必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定:
(1)必须以vnd开头。
(2)如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/。
(3)最后接上vnd.<authority>.<path>
讲完了这些重要的概念,接下来我们就来自己创建一个内容提供器。
第一步:创建数据库以及对应的表
/**
* author: zhoufan
* data: 2021/6/18 16:42
* content:
*/
class MyDatabaseHelper(
context: Context?,
name: String?,
factory: SQLiteDatabase.CursorFactory?,
version: Int
) : SQLiteOpenHelper(context, name, factory, version) {
private var mContext: Context? = null
init {
mContext = context
}
// 创建SQL语句
var CREATE_BOOK =
"create table Book (" + "id integer primary key autoincrement, " + "author text, " + "price real, " + "pages integer," + "name text)"
var CREATE_CATEGORY =
"create table Category (" + "id integer primary key autoincrement, " + "category_name text, " + "category_code integer)"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(CREATE_BOOK)
db?.execSQL(CREATE_CATEGORY)
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
第二步:创建一个类继承ContentProvider
/**
* author: zhoufan
* data: 2021/6/18 16:10
* content:自定义内容提供者
*/
class MyProvider : ContentProvider() {
private val BOOK_DIR = 0
private val BOOK_ITEM = 1
private val CATEGORY_DIR = 2
private val CATEGORY_ITEM = 3
private val AUTHORITY = "com.steven.sunworld.provider"
private var uriMatcher: UriMatcher? = null
private var dbHelper: MyDatabaseHelper? = null
init {
uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
uriMatcher!!.addURI(AUTHORITY, "book", BOOK_DIR)
uriMatcher!!.addURI(AUTHORITY, "book/#", BOOK_ITEM)
uriMatcher!!.addURI(AUTHORITY, "category", CATEGORY_DIR)
uriMatcher!!.addURI(AUTHORITY, "category/#", CATEGORY_ITEM)
}
/**
* 初始化的时候调用,通常会在这里完成对数据库的创建和升级
* 返回true表示创建成功,false表示创建失败
* 注意只有当存在ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化
*/
override fun onCreate(): Boolean {
dbHelper = MyDatabaseHelper(context, "BookStore.db", null, 2)
return true
}
/**
* 从内容提供器中查询数据。
* uri:查询哪张表
* projection:查询哪一列
* selection/selectionArgs:约束查询行
* sortOrder:对结果进行排序
*/
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper!!.readableDatabase
var cursor: Cursor? = null
when (uriMatcher?.match(uri)) {
BOOK_DIR -> {
cursor =
db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
}
BOOK_ITEM -> {
val bookId = uri.pathSegments[1]
cursor =
db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
}
CATEGORY_DIR -> {
cursor = db.query(
"Category",
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
}
CATEGORY_ITEM -> {
val categoryId = uri.pathSegments[1]
cursor = db.query(
"Category",
projection,
"id = ?",
arrayOf(categoryId),
null,
null,
sortOrder
)
}
}
return cursor
}
/**
* 向内容提供者添加一条数据
* uri:插入到哪张表
* values:插入的值
*/
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper?.writableDatabase
var uriReturn: Uri? = null
when (uriMatcher?.match(uri)) {
BOOK_DIR, BOOK_ITEM -> {
val newBookId = db?.insert("Book", null, values)
uriReturn = Uri.parse("content://$AUTHORITY/book/$newBookId")
}
CATEGORY_DIR, CATEGORY_ITEM -> {
val newCategoryId = db?.insert("Category", null, values)
uriReturn = Uri.parse("content://$AUTHORITY/category/$newCategoryId")
}
}
return uriReturn
}
/**
* 更新内容提供者中已有的数据
* uri:更新哪一张表的数据
* values:更新的数据
* selection/selectionArgs:约束更新行
*/
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
val db = dbHelper?.writableDatabase
var updatedRows = 0
when (uriMatcher!!.match(uri)) {
BOOK_DIR -> {
updatedRows = db!!.update("Book", values, selection, selectionArgs)
}
BOOK_ITEM -> {
val bookId = uri.pathSegments[1]
updatedRows = db!!.update("Book", values, "id=?", arrayOf(bookId))
}
CATEGORY_DIR -> {
updatedRows = db!!.update("Category", values, selection, selectionArgs)
}
CATEGORY_ITEM -> {
val categoryId = uri.pathSegments[1]
updatedRows = db!!.update("Category", values, "id=?", arrayOf(categoryId))
}
}
return updatedRows
}
/**
* 从内容提供者中删除数据
* uri:删除哪一张表的数据
* selection/selectionArgs:约束删除行
*/
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
val db = dbHelper?.writableDatabase
var deletedRows = 0
when (uriMatcher!!.match(uri)) {
BOOK_DIR -> {
deletedRows = db!!.delete("Book", selection, selectionArgs)
}
BOOK_ITEM -> {
val bookId = uri.pathSegments[1]
deletedRows = db!!.delete("Book", "id=?", arrayOf(bookId))
}
CATEGORY_DIR -> {
deletedRows = db!!.delete("Category", selection, selectionArgs)
}
CATEGORY_ITEM -> {
val categoryId = uri.pathSegments[1]
deletedRows = db!!.delete("Category", "id=?", arrayOf(categoryId))
}
}
return deletedRows
}
/**
* 返回的MIME类型
*/
override fun getType(uri: Uri): String? {
var type: String? = null
when (uriMatcher?.match(uri)) {
BOOK_DIR -> {
type =
"vnd.android.cursor.dir/vnd.com.steven.sunworld.provider.book"
}
BOOK_ITEM -> {
type =
"vnd.android.cursor.item/vnd.com.steven.sunworld.provider.book"
}
CATEGORY_DIR -> {
type =
"vnd.android.cursor.dir/vnd.com.steven.sunworld.provider.category"
}
CATEGORY_ITEM -> {
type =
"vnd.android.cursor.item/vnd.com.steven.sunworld.provider.category"
}
}
return type
}
}
第三步:插入数据
fun addData(view: View) {
val uri = Uri.parse("content://com.steven.sunworld.provider/book")
val values = ContentValues()
values.put("name", "A Clash of kings")
values.put("author", "George Martin")
values.put("pages", 1040)
values.put("price", 22.85)
val newUri = contentResolver.insert(uri, values)
val newId = newUri!!.pathSegments[1]
Log.i("zhoufan", newId.toString())
}
第四步:检验结果,查询数据
fun queryData(view: View) {
val uri = Uri.parse("content://com.steven.sunworld.provider/book")
val cursor = contentResolver.query(uri, null, null, null, null)
if (cursor != null) {
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("author"))
Log.i("zhoufan", name + author + pages + price)
}
cursor.close()
}
}
最后别忘记在清单文件里面注册我们自己定义的ContentProvider
<provider
android:name=".contentprovider.MyProvider"
android:authorities="com.steven.sunworld.provider"
android:enabled="true"
android:exported="true" />
到这里,自定义的ContentProvider就完成了,需要注意的是authorities在全局里面要保持一致,一般情况下都是以包名+provider来定义。