ContentProvider IPC
问题
ContentProvider 作为 IPC 机制有什么特点?底层是如何实现跨进程数据共享的?
答案
ContentProvider 的 IPC 特点
ContentProvider 是 Android 四大组件之一,底层通过 Binder 实现跨进程通信,但提供了统一的 CRUD 接口,适合数据共享场景:
跨进程使用
// App A(访问通讯录数据)
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
arrayOf(
ContactsContract.Contacts.DISPLAY_NAME
),
null, null,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(0)
}
}
自定义 ContentProvider
class BookProvider : ContentProvider() {
private lateinit var db: BookDatabase
companion object {
const val AUTHORITY = "com.example.bookprovider"
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/books")
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, "books", 1) // 匹配所有书籍
addURI(AUTHORITY, "books/#", 2) // 匹配单本书籍
}
}
override fun onCreate(): Boolean {
db = Room.databaseBuilder(context!!, BookDatabase::class.java, "books.db").build()
return true
}
override fun query(uri: Uri, projection: Array<String>?,
selection: String?, selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
return when (uriMatcher.match(uri)) {
1 -> db.bookDao().queryAllAsCursor()
2 -> {
val id = ContentUris.parseId(uri)
db.bookDao().queryByIdAsCursor(id)
}
else -> null
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = db.bookDao().insert(Book.fromContentValues(values!!))
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(CONTENT_URI, id)
}
// update / delete 类似...
override fun update(uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?): Int = 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
override fun getType(uri: Uri): String = "vnd.android.cursor.dir/vnd.example.books"
}
ContentProvider 线程模型
线程安全
ContentProvider 的 query/insert/update/delete 运行在 Binder 线程池中,多个 Client 同时访问时会并发执行。如果底层是 SQLite,默认已通过 SQLiteDatabase 的锁机制保证安全。使用 Room 则天然线程安全。
ContentProvider vs 直接 AIDL
| 特性 | ContentProvider | AIDL |
|---|---|---|
| 接口 | 固定 CRUD | 自定义 |
| 数据类型 | Cursor / ContentValues | Parcelable |
| 权限控制 | <permission> 声明式 | 需自行实现 |
| URI 寻址 | 统一 content:// | 需 bindService |
| 适用 | 数据共享 | 远程方法调用 |
常见面试问题
Q1: ContentProvider 的 onCreate 在哪个线程执行?何时执行?
答案:
ContentProvider.onCreate() 在主线程执行,且在 Application.onCreate() 之前。因此应避免在 ContentProvider.onCreate() 中做耗时操作,否则会拖慢应用启动速度。
Q2: ContentProvider 如何实现数据变化通知?
答案:
- 数据变化时:调用
contentResolver.notifyChange(uri, null)通知 - 观察者注册:Client 通过
contentResolver.registerContentObserver(uri, true, observer)监听 - Cursor 自动通知:返回 Cursor 前调用
cursor.setNotificationUri(contentResolver, uri),配合CursorLoader/LiveData自动感知变化