跳到主要内容

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

特性ContentProviderAIDL
接口固定 CRUD自定义
数据类型Cursor / ContentValuesParcelable
权限控制<permission> 声明式需自行实现
URI 寻址统一 content://需 bindService
适用数据共享远程方法调用

常见面试问题

Q1: ContentProvider 的 onCreate 在哪个线程执行?何时执行?

答案

ContentProvider.onCreate()主线程执行,且在 Application.onCreate() 之前。因此应避免在 ContentProvider.onCreate() 中做耗时操作,否则会拖慢应用启动速度。

Q2: ContentProvider 如何实现数据变化通知?

答案

  1. 数据变化时:调用 contentResolver.notifyChange(uri, null) 通知
  2. 观察者注册:Client 通过 contentResolver.registerContentObserver(uri, true, observer) 监听
  3. Cursor 自动通知:返回 Cursor 前调用 cursor.setNotificationUri(contentResolver, uri),配合 CursorLoader / LiveData 自动感知变化

相关链接