一、核心概念
1. 主要作用
- 数据抽象:隐藏数据存储细节(如数据库表结构),通过 URI 和 ContentResolver 统一访问。
- 跨应用共享:安全地允许其他应用读写数据(需权限控制)。
- 数据监控:通过
ContentObserver
监听数据变化(如短信数据库更新)。
2. 关键组件
ContentProvider
:数据提供者,实现query()
、insert()
等方法。ContentResolver
:客户端通过它访问 ContentProvider。Uri
:唯一标识数据资源,格式为content://authority/path/id
。
二、自定义 ContentProvider 实现步骤
1. 定义数据模型与 Contract 类
kotlin
// 定义数据表结构和 URI 常量
object BookContract {
const val AUTHORITY = "com.example.provider.bookprovider"
val CONTENT_URI = Uri.parse("content://$AUTHORITY/books")
object BookEntry {
const val TABLE_NAME = "books"
const val _ID = "_id"
const val COLUMN_TITLE = "title"
const val COLUMN_AUTHOR = "author"
}
}
2. 创建 ContentProvider 子类
kotlin
class BookProvider : ContentProvider() {
private lateinit var dbHelper: SQLiteOpenHelper
// Uri 匹配器,处理不同 URI 请求
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(BookContract.AUTHORITY, "books", BOOKS)
addURI(BookContract.AUTHORITY, "books/#", BOOK_ID)
}
companion object {
const val BOOKS = 1
const val BOOK_ID = 2
}
override fun onCreate(): Boolean {
dbHelper = BookDbHelper(context!!)
return true
}
// 实现 CRUD 方法...
}
3. 实现 CRUD 操作
kotlin
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
BOOKS -> db.query(
BookContract.BookEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
BOOK_ID -> {
val id = uri.lastPathSegment
db.query(
BookContract.BookEntry.TABLE_NAME,
projection,
"${BookContract.BookEntry._ID} = ?",
arrayOf(id),
null,
null,
sortOrder
)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
val id = when (uriMatcher.match(uri)) {
BOOKS -> db.insert(BookContract.BookEntry.TABLE_NAME, null, values)
else -> throw IllegalArgumentException("Unsupported URI: $uri")
}
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
// 类似实现 update()、delete() 和 getType()
4. 注册 Provider
在 AndroidManifest.xml
中声明:
xml
<provider
android:name=".BookProvider"
android:authorities="com.example.provider.bookprovider"
android:exported="true" <!-- 允许其他应用访问 -->
android:readPermission="com.example.permission.READ_BOOKS"
android:writePermission="com.example.permission.WRITE_BOOKS"/>
三、客户端通过 ContentResolver 访问数据
1. 查询数据
kotlin
val cursor = contentResolver.query(
BookContract.CONTENT_URI,
arrayOf(BookContract.BookEntry.COLUMN_TITLE, BookContract.BookEntry.COLUMN_AUTHOR),
null,
null,
null
)
cursor?.use {
while (it.moveToNext()) {
val title = it.getString(it.getColumnIndex(BookContract.BookEntry.COLUMN_TITLE))
val author = it.getString(it.getColumnIndex(BookContract.BookEntry.COLUMN_AUTHOR))
Log.d("BookProvider", "Title: $title, Author: $author")
}
}
2. 插入数据
kotlin
val values = ContentValues().apply {
put(BookContract.BookEntry.COLUMN_TITLE, "Kotlin in Action")
put(BookContract.BookEntry.COLUMN_AUTHOR, "Dmitry Jemerov")
}
val uri = contentResolver.insert(BookContract.CONTENT_URI, values)
Log.d("BookProvider", "Inserted URI: $uri")
四、权限控制
1. 声明自定义权限
在提供方应用的 AndroidManifest.xml
:
xml
<permission
android:name="com.example.permission.READ_BOOKS"
android:protectionLevel="signature" />
<permission
android:name="com.example.permission.WRITE_BOOKS"
android:protectionLevel="signature|dangerous" />
2. 客户端申请权限
在客户端应用的 AndroidManifest.xml
:
xml
<uses-permission android:name="com.example.permission.READ_BOOKS" />
<uses-permission android:name="com.example.permission.WRITE_BOOKS" />
五、高级功能
1. 使用 ContentObserver 监听数据变化
kotlin
// 注册观察者
val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
Log.d("BookProvider", "Data changed: $uri")
}
}
contentResolver.registerContentObserver(
BookContract.CONTENT_URI,
true, // 监听所有子 URI
observer
)
// 取消注册
contentResolver.unregisterContentObserver(observer)
2. 处理文件数据(流传输)
实现 openFile()
方法,用于共享文件:
kotlin
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
val file = File(context?.filesDir, uri.lastPathSegment)
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
}
六、与 Room 数据库集成
1. 在 Room 中暴露 ContentProvider
kotlin
@Database(entities = [Book::class], version = 1)
abstract class BookDatabase : RoomDatabase() {
abstract fun bookDao(): BookDao
companion object {
private var instance: BookDatabase? = null
fun getInstance(context: Context): BookDatabase {
return instance ?: synchronized(this) {
instance ?: Room.databaseBuilder(
context,
BookDatabase::class.java, "book_database"
).build()
}
}
}
}
// 在 ContentProvider 中通过 BookDatabase 操作数据
七、注意事项
线程安全:
- ContentProvider 方法默认在主线程调用,需自行处理耗时操作(如启动协程)。
- 使用
@WorkerThread
注解提示调用者。
URI 设计:
- 使用
UriMatcher
管理不同 URI 路径。 - 支持批量操作 (
applyBatch()
) 提升效率。
- 使用
Android 11+ 包可见性:
- 若目标应用为 Android 11 (API 30) 及以上,需在客户端声明提供方包名:xml
<queries> <package android:name="com.example.provider" /> </queries>
- 若目标应用为 Android 11 (API 30) 及以上,需在客户端声明提供方包名:
性能优化:
- 使用
CursorLoader
(或LoaderManager
)在后台加载数据。 - 限制返回数据量(如分页查询)。
- 使用
八、常见应用场景
- 系统数据访问:读取联系人、日历事件。
- 应用插件化:主应用与插件间数据交换。
- 数据备份/同步:通过自定义 Provider 暴露数据给备份服务。
- 跨应用文件共享:使用
FileProvider
(ContentProvider 的子类)。
通过合理设计 ContentProvider,可实现安全高效的数据共享,同时遵循 Android 的最佳安全实践。