前言
ContentProvider 是 Android 的四大组件之一,有时候我们需要操作其他应用程序的一些数据,就会用到 ContentProvider,ContentProvider 本质上是一个中间者,真正 存储和操作数据 的数据源还是原来存储数据的方式,如数据库、文件或网络等。
ContentProvider 以相对安全的方式封装了数据并提供简易的处理机制和统一的访问接口供其他程序调用。它的底层采用了 Binder 机制来实现,ContentProvider 为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用进行增删改查,而不用担心因为直接开放数据库权限而带来的安全问题。当然,ContentProvider不仅可以实现跨进程通信,也可实现进程内的通信。
在 Android 中,为一些常见的数据提供了默认的 ContentProvider,如通讯录等。
ContentProvider 是一个抽象类,如果我们需要自定义内容提供者我们就需要继承 ContentProvider 类并复写它的方法,如下:
class MyContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
}
override fun getType(uri: Uri): String? {
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
}
}
注意:访问数据的方法 insert,delete 和 update 可能被多个线程同时调用,操作数据时,务必要保证线程是安全的。
在了解 ContentProvider 使用之前,我们需要对其涉及到的一些概念有一定的了解,如 URI、MIME 等。
URI
URI,Universal Resource Identifier,统一资源定位符。其它应用可以通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据。
URI 为系统中的每一个资源赋予一个名字,比方说通话记录。
每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。
URI 的格式如下:
- scheme,标准前缀,一般就是 content://
- host:port,URI 的标识,如:com.yang.provider.myprovider 。用于标识 ContentProvider,外部调用者可根据标识来找到它。标识必须是完整的、小写的类名。一般是
包.类 - path,表示要操作的数据库中表的名字,也可以自己定义,记得在使用的时候保持一致就可以了
- query,表示要查询的表中的某条索引对应的数据。如果不写这个参数,就表示返回表中全部数据
URI 的示例如下:
[scheme:][
content://com.yang.provider.myprovider/tablename/id
MIME
MIME 是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看 PDF 格式的文件,浏览器会选择合适的应用来打开一样。
Android ContentProvider 会根据 URI 来返回 MIME 类型,ContentProvider 会返回一个包含两部分的字符串。
MIME 类型一般包含两部分,如:
text/html
text/css
text/xml
application/pdf
分为类型和子类型,Android 遵循类似的约定来定义MIME类型,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。
- 集合记录(dir):
vnd.android.cursor.dir/自定义 - 单条记录(item):
vnd.android.cursor.item/自定义
vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。
在使用 Intent 时,也会用到 MIME,根据 Mimetype 打开符合条件的活动。
在 ContentProvider 中,我们可以根据 URI 返回MIME 类型,代码如下:
val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
val type: String? = requireActivity().contentResolver.getType(uri)
UriMatcher and ContentUris
Uri 代表要操作的数据,在开发过程中对数据进行获取时需要解析 Uri 。
Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。
UriMatcher
UriMatcher 类用于匹配 Uri,它的使用步骤如下:
**步骤一:**将需要匹配的Uri路径进行注册,代码如下:
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sMatcher.addURI("content://com.yang.provider.myprovider", " tablename ", 1);
sMatcher.addURI("com.yang.provider.myprovider", "tablename/#", 2);
路径后面的 id 采用了通配符形式 # ,表示只要前面三个部分都匹配上就算是匹配成功。
**步骤二:**使用 sMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码,即 addURI() 方法的第三个参数
int code = sMatcher.match(Uri.parse("content://com.yang.provider.myprovider/tablename/100"))
switch (code) {
case 1:
break;
case 2
break;
default:
break;
}
ContentUris
ContentUris 类用于操作 Uri 路径后面的 ID 部分,它有两个比较实用的方法:
withAppendedId(Uri uri, long id) :用于为路径加上 ID 部分,如:
Uri uri = Uri.parse("content://com.yang.provider.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
parseId(Uri uri) :用于从路径中获取 ID 部分,如:
Uri uri = Uri.parse("com.yang.provider.myprovider/user/7")
long personid = ContentUris.parseId(uri);
ContentResolver
其他 app 或进程想要操作 ContentProvider ,需要先获取其相应的 ContentResolver ,再利用 ContentResolver 类来完成对数据的增删改查操作。
为什么要使用通过 ContentResolver 类从而与 ContentProvider 类进行交互,而不直接访问ContentProvider 类呢?ContentResolver 类是对所有的 ContentProvider 进行统一管理的,这样,调用者就不必了解每个不同的 ContentProvider 的实现,只需要关注如何通过 ContentResolver 操作 ContentProvider 就行了。
使用 ContentResolver 对 ContentProvider 中的数据进行操作的代码如下:
val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
val resolver: ContentResolver = requireActivity().contentResolver
resolver.insert(uri, ContentValues().apply {
put("name", "yang")
put("age", 18)
})
val cursor = resolver.query(uri, null, null, null, "tablename data")
while (cursor!!.moveToNext()) {
}
val updateIdUri = ContentUris.withAppendedId(uri, 1)
resolver.update(updateIdUri, ContentValues().apply {
put("name", "zhang1")
}, null, null)
val deleteIdUri = ContentUris.withAppendedId(uri, 2)
resolver.delete(deleteIdUri, null, null)
如果 ContentProvider 的访问者需要知道数据发生的变化,可以在 ContentProvider 发生数据变化时调用如下代码通知注册在此 URI 上的访问者:
val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
requireActivity().contentResolver.notifyChange(uri, null)
同时,访问者需使用 ContentObserver 对数据进行监听:
private val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
}
}
private val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
requireActivity().contentResolver.registerContentObserver(uri, true, observer)
requireActivity().contentResolver.unregisterContentObserver(observer)
代码演示
进程内通信 Demo 演示
步骤一:创建数据库类,数据库中存在两个表,分别是 user 和 job 表。
class DBHelper(
context: Context
) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
db.execSQL("CREATE TABLE IF NOT EXISTS $JOB_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, job TEXT)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}
companion object {
private const val DATABASE_NAME = "finch.db"
const val USER_TABLE_NAME = "user"
const val JOB_TABLE_NAME = "job"
private const val DATABASE_VERSION = 1
}
}
步骤二:创建一个 MyContentProvider ,继承自 ContentProvider 抽象类。其中,在 onCreate() 方法中,先对数据库初始化,并往数据库中的 user 表和 job 表分别添加两条数据。
class MyContentProvider : ContentProvider() {
private lateinit var mDbHelper: DBHelper
private lateinit var db: SQLiteDatabase
private val mMatcher: UriMatcher by lazy {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(AUTHORITY, "user", User_Code)
matcher.addURI(AUTHORITY, "job", Job_Code)
matcher
}
override fun onCreate(): Boolean {
mDbHelper = DBHelper(context!!)
db = mDbHelper.writableDatabase
db.execSQL("delete from user")
db.execSQL("insert into user values(1,'yang');")
db.execSQL("insert into user values(2,'zhang');")
db.execSQL("delete from job")
db.execSQL("insert into job values(1,'Android');")
db.execSQL("insert into job values(2,'iOS');")
return true
}
override fun insert(uri: Uri, values: ContentValues?): Uri {
val table = getTableName(uri)
db.insert(table, null, values)
context?.contentResolver?.notifyChange(uri, null)
return uri
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val table = getTableName(uri)
return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null)
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
return 0
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
override fun getType(uri: Uri): String? {
return null
}
private fun getTableName(uri: Uri): String? {
var tableName: String? = null
when (mMatcher.match(uri)) {
User_Code -> tableName = DBHelper.USER_TABLE_NAME
Job_Code -> tableName = DBHelper.JOB_TABLE_NAME
}
return tableName
}
companion object {
private const val AUTHORITY = "com.yang.provider.myprovider"
const val User_Code = 1
const val Job_Code = 2
}
}
步骤三:在 androidManifest.xml 中注册 MyContentProvider
<provider
android:name=".fragment.MyContentProvider"
android:authorities="com.yang.provider.myprovider" />
步骤四:进程内使用 ContentResolver 操作 ContentProvider。
private fun operateData() {
val userUri = Uri.parse("content://com.yang.provider.myprovider/user")
val jobUri = Uri.parse("content://com.yang.provider.myprovider/job")
contentResolver.insert(userUri, ContentValues().apply {
put("_id", 3)
put("name", "chen")
})
val cursor = contentResolver.query(userUri, arrayOf("_id", "name"), null, null, null)
while (cursor!!.moveToNext()) {
println("query user:" + cursor.getInt(0) + " " + cursor.getString(1))
}
cursor.close()
contentResolver.insert(jobUri, ContentValues().apply {
put("_id", 3)
put("job", "Web")
})
val cursor2 = contentResolver.query(jobUri, arrayOf("_id", "job"), null, null, null)
while (cursor2!!.moveToNext()) {
println("query job:" + cursor2.getInt(0) + " " + cursor2.getString(1))
}
cursor2.close()
}
I/System.out: query user:1 yang
I/System.out: query user:2 zhang
I/System.out: query user:3 chen
I/System.out: query job:1 Android
I/System.out: query job:2 iOS
I/System.out: query job:3 Web
跨进程通信 Demo 演示
步骤一:创建数据库类,数据库中存在两个表,分别是 user 和 job 表。
class DBHelper(
context: Context
) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
db.execSQL("CREATE TABLE IF NOT EXISTS $JOB_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, job TEXT)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}
companion object {
private const val DATABASE_NAME = "finch.db"
const val USER_TABLE_NAME = "user"
const val JOB_TABLE_NAME = "job"
private const val DATABASE_VERSION = 1
}
}
步骤二:创建一个 MyContentProvider ,继承自 ContentProvider 抽象类。其中,在 onCreate() 方法中,先对数据库初始化,并往数据库中的 user 表和 job 表分别添加两条数据。
class MyContentProvider : ContentProvider() {
private lateinit var mDbHelper: DBHelper
private lateinit var db: SQLiteDatabase
private val mMatcher: UriMatcher by lazy {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(AUTHORITY, "user", User_Code)
matcher.addURI(AUTHORITY, "job", Job_Code)
matcher
}
override fun onCreate(): Boolean {
mDbHelper = DBHelper(context!!)
db = mDbHelper.writableDatabase
db.execSQL("delete from user")
db.execSQL("insert into user values(1,'yang');")
db.execSQL("insert into user values(2,'zhang');")
db.execSQL("delete from job")
db.execSQL("insert into job values(1,'Android');")
db.execSQL("insert into job values(2,'iOS');")
return true
}
override fun insert(uri: Uri, values: ContentValues?): Uri {
val table = getTableName(uri)
db.insert(table, null, values)
context?.contentResolver?.notifyChange(uri, null)
return uri
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val table = getTableName(uri)
return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null)
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
return 0
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
override fun getType(uri: Uri): String? {
return null
}
private fun getTableName(uri: Uri): String? {
var tableName: String? = null
when (mMatcher.match(uri)) {
User_Code -> tableName = DBHelper.USER_TABLE_NAME
Job_Code -> tableName = DBHelper.JOB_TABLE_NAME
}
return tableName
}
companion object {
private const val AUTHORITY = "com.yang.provider.myprovider"
const val User_Code = 1
const val Job_Code = 2
}
}
步骤三:在 androidManifest.xml 中注册 MyContentProvider
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx.xxxx">
<permission
android:name="com.yang.provider.myprovider.PROVIDER"
android:protectionLevel="normal" />
<permission
android:name="com.yang.provider.myprovider.Read"
android:protectionLevel="normal" />
<permission
android:name="com.yang.provider.myprovider.Write"
android:protectionLevel="normal" />
<application>
<provider
android:name=".MyContentProvider"
android:authorities="com.yang.provider.myprovider"
android:exported="true"
android:permission="com.yang.provider.myprovider.PROVIDER"
// android:readPermission="com.yang.provider.myprovider.Read"
// android:writePermission="com.yang.provider.myprovider.Write"
/>
</application>
</manifest>
至此,创建进程一的代码编写完毕。
接下来继续编写进程二的代码,步骤如下。
步骤一:声明访问进行 1 中的 ContentProvider 所需要的权限
<uses-permission android:name="com.yang.provider.myprovider.PROVIDER" />
步骤二:使用 ContentResolver 操作 进程 1 的 ContentProvider。
private fun operateData() {
val userUri = Uri.parse("content://com.yang.provider.myprovider/user")
val jobUri = Uri.parse("content://com.yang.provider.myprovider/job")
contentResolver.insert(userUri, ContentValues().apply {
put("_id", 4)
put("name", "huang")
})
val cursor = contentResolver.query(userUri, arrayOf("_id", "name"), null, null, null)
while (cursor!!.moveToNext()) {
println("ipc query user:" + cursor.getInt(0) + " " + cursor.getString(1))
}
cursor.close()
contentResolver.insert(jobUri, ContentValues().apply {
put("_id", 4)
put("job", "algorithm")
})
val cursor2 = contentResolver.query(jobUri, arrayOf("_id", "job"), null, null, null)
while (cursor2!!.moveToNext()) {
println("ipc query job:" + cursor2.getInt(0) + " " + cursor2.getString(1))
}
cursor2.close()
}
I/System.out: ipc query user:1 yang
I/System.out: ipc query user:2 zhang
I/System.out: ipc query user:4 huang
I/System.out: ipc query job:1 Android
I/System.out: ipc query job:2 iOS
I/System.out: ipc query job:4 algorithm
|