概述
ContentProvider 主要用于在不同的应用程序之间实现数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序的数据,同时还能保证数据的安全
ContentProvider 的用法一般有两种:一种是使用现在的 ContentProvider 读取和操作相应程序中的数据;另一种是创建自己的 ContentProvider,给程序的数据提供外部访问接口
ContentResolver
对于一个应用程序来说,要想访问 ContentProvider 中共享的数据,就要借助 ContentResolver 类,可以通过 Context 中的 getContentResolver() 方法获取该类的实例
ContentResolver 提供了一系列方法用于对数据进行增删改查,与 SQLiteDatabase 相似。但不同的是,ContentResolver 中的增删改查方法不接收表名参数,而是使用一个 Uri 参数代替,这个参数称为内容 URI
内容 URI 给 ContentProvider 中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path,authority 用于区分不同的应用程序,一般采用应用包名的方式进行命名,比如某个应用的包名是 com.example.app ,那么 authority 就可以命名为 com.example.app.provider 。path 则是用于对同一程序中不同的表做区分,通常添加到 authority 的后面,比如某个应用程序的数据库存在两个 table1 和 table2,这时可以将 path 分别命名为 /table1 和 /table2。然后将 authority 和 path 进行组合,内容 URI 就变成了 com.example.app.provider/table1 和 com.example.app.provider/table2 。我们还需在字符串的头部加上协议声明,因此内容 URI 最标准格式如下:content://com.example.app.provider/table1 和 content://com.example.app.provider/table1
在得到内容 URI 字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入,代码如下:
val uri = Uri.parse("content://com.example.app.provider/table1")
再使用这个 Uri 对象查询 table1 表中的数据,代码如下:
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder)
下表是 query() 方法的参数说明
query() 方法参数 | 对应 SQL 部分 | 描述 |
---|
uri | from table_name | 指定查询某个应用程序下的某一张表 | projection | select column1, column2 | 指定查询的列名 | selection | where column = value | 指定 where 的约束条件 | selectionArgs | | 为 where 中的占位符提供具体的值 | sortOrder | order by column1, column2 | 指定查询结果的排序方式 |
根据返回的 Cursor 对象,我们就可以将数据从 Cursor 对象中遍历读取出来了
while(cursor.moveToNext()) {
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
掌握了查询操作,剩下的增加、修改、删除操作就更不在话下了
查询操作是将待添加的数据组装到 ContentValues 中,然后调用 ContentResolver 的 insert() 方法
val values = contentValuesOf("column1" to "next", "column2" to 1)
contentResolver.insert(uri, values)
如果我们想要更新这条新添加的数据,然后将 column1 的值清空,可以借助 ContentResolver 的 update() 方法实现
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
最后,可以调用 ContentResolver 的 delete() 方法将这条数据删除
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
ContentProvider
如果希望自己的程序的数据能被共享,可以新建一个类去继承 ContentProvider 的方式去实现。ContentProvider 类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写
class MyProvider : ContentProvider() {
override fun insert(uri: Uri, values: ContentValues?): Uri? {
return null
}
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
return null
}
override fun onCreate(): Boolean {
return false
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
return 0
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
return 0
}
override fun getType(uri: Uri): String? {
return null
}
}
可以看到,很多方法里带有 uri 这个参数,这个参数也正是调用 ContentResolver 的增删改查方法时传递过来的。我们需要对传入的 uri 参数进行解析,从中分析出调用方期望访问的表和数据
我们可以在这个内容 URI 的后面加上一个 id
content://com.example.app.provider/table1/1
表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据
我们还可以使用通配符分别匹配这两种格式的内容 URI,规则如下
- * 表示匹配任意长度的任意字符
- # 表示匹配任意长度的数字
一个能匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/#
接着,我们再借助 UriMatcher 这个类就可以实现匹配内容 Uri 的功能,该类提供了一个 addURI() 方法,这个方法接收三个参数,可以分别把 authority、path 和一个自定义代码传进去,当调用 UriMatcher 的 match() 方法时,就可以将一个 Uri 对象传进去,返回值是某个能匹配这个 Uri 对象所对应的自定义代码
class MyProvider : ContentProvider() {
private val table1Dir = 0
private val table1Item = 1
private val table2Dir = 2
private val table2Item = 3
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
uriMatcher.addURI("com.example.app.provider", "table1", table1Dir)
uriMatcher.addURI("com.example.app.provider", "table1/#", table1Item)
uriMatcher.addURI("com.example.app.provider", "table2", table2Dir)
uriMatcher.addURI("com.example.app.provider", "table2/#", table2Item)
}
...
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
when (uriMatcher.match(uri)) {
table1Dir -> {
}
table1Item -> {
}
table2Dir -> {
}
table2Item -> {
}
}
}
}
上述代码只是以 query() 方法为例做了个示范,其实 insert()、update()、delete() 这几个方法的实现是差不多的
除此以外,还有 getType() 方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由三个部分组成,Android 对这三个部分做了如下格式规定:
-
必须以 vnd 开头 -
如果内容 URI 以路径结尾,则后接 android.cursor.dir/ 如果内容 URI 以 id 结尾,则后接 android.cursor.item/ -
最后接上 vnd.<authority>.<path>
继续完善 MyProvider 的内容,实现 getType() 方法中的逻辑
class MyProvider : ContentProvider() {
...
override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
else -> null
}
}
|