为什么需要 ContentProvider
Android 应用间默认是隔离的,每个 App 运行在独立进程中。当你需要:
- 把自己的数据共享给其他 App(联系人、相册)
- 访问系统或第三方 App 的数据(通讯录、短信)
- 统一数据访问接口,隐藏底层实现(SQLite / 文件 / 网络)
就需要 ContentProvider 作为跨进程数据通道。
核心概念
URI 定位
content://com.example.app.provider/users/1
↓ ↓ ↓
authority table id
authority:全局唯一标识符,通常用包名path:指定数据类型(表名)id:可选,指定具体记录
CRUD 方法
query() // 查询
insert() // 插入
update() // 更新
delete() // 删除
getType() // 返回 MIME 类型
实战:创建自己的 Provider
1. 定义 Provider 类
class UserProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.provider"
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/users")
private const val USERS = 1
private const val USER_ID = 2
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, "users", USERS)
addURI(AUTHORITY, "users/#", USER_ID)
}
}
private lateinit var db: SQLiteDatabase
override fun onCreate(): Boolean {
db = context!!.openOrCreateDatabase("users.db", Context.MODE_PRIVATE, null)
db.execSQL("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
return when (uriMatcher.match(uri)) {
USERS -> db.query("users", projection, selection, selectionArgs, null, null, sortOrder)
USER_ID -> {
val id = uri.lastPathSegment
db.query("users", projection, "id=?", arrayOf(id), null, null, sortOrder)
}
else -> null
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = db.insert("users", null, values)
context?.contentResolver?.notifyChange(uri, null)
return Uri.withAppendedPath(CONTENT_URI, id.toString())
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
val count = when (uriMatcher.match(uri)) {
USERS -> db.update("users", values, selection, selectionArgs)
USER_ID -> {
val id = uri.lastPathSegment
db.update("users", values, "id=?", arrayOf(id))
}
else -> 0
}
if (count > 0) context?.contentResolver?.notifyChange(uri, null)
return count
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val count = when (uriMatcher.match(uri)) {
USERS -> db.delete("users", selection, selectionArgs)
USER_ID -> {
val id = uri.lastPathSegment
db.delete("users", "id=?", arrayOf(id))
}
else -> 0
}
if (count > 0) context?.contentResolver?.notifyChange(uri, null)
return count
}
override fun getType(uri: Uri): String {
return when (uriMatcher.match(uri)) {
USERS -> "vnd.android.cursor.dir/vnd.com.example.provider.users"
USER_ID -> "vnd.android.cursor.item/vnd.com.example.provider.users"
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
}
2. 注册到 AndroidManifest.xml
<provider
android:name=".UserProvider"
android:authorities="com.example.provider"
android:exported="true"
android:permission="com.example.READ_USERS" />
exported="true":允许其他应用访问permission:可选,限制访问权限
3. 其他应用访问
// 查询所有用户
val cursor = contentResolver.query(
Uri.parse("content://com.example.provider/users"),
arrayOf("id", "name", "age"),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val id = it.getInt(0)
val name = it.getString(1)
val age = it.getInt(2)
Log.d("User", "$id: $name, $age")
}
}
// 插入新用户
val values = ContentValues().apply {
put("name", "张三")
put("age", 25)
}
val newUri = contentResolver.insert(
Uri.parse("content://com.example.provider/users"),
values
)
// 更新用户
val updateValues = ContentValues().apply {
put("age", 26)
}
contentResolver.update(
Uri.parse("content://com.example.provider/users/1"),
updateValues, null, null
)
// 删除用户
contentResolver.delete(
Uri.parse("content://com.example.provider/users/1"),
null, null
)
监听数据变化
val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean) {
Log.d("Provider", "数据已更新")
// 重新查询
}
}
contentResolver.registerContentObserver(
Uri.parse("content://com.example.provider/users"),
true, observer
)
// 记得注销
contentResolver.unregisterContentObserver(observer)
权限控制
1. 定义自定义权限
<permission
android:name="com.example.READ_USERS"
android:protectionLevel="normal" />
2. 在 Provider 中声明
<provider
android:readPermission="com.example.READ_USERS"
android:writePermission="com.example.WRITE_USERS" />
3. 调用方申请权限
<uses-permission android:name="com.example.READ_USERS" />
常见场景
访问系统联系人
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
Log.d("Contact", name)
}
}
需要权限:<uses-permission android:name="android.permission.READ_CONTACTS" />
访问相册
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, null, null, "${MediaStore.Images.Media.DATE_ADDED} DESC"
)
cursor?.use {
while (it.moveToNext()) {
val id = it.getLong(0)
val name = it.getString(1)
val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
Log.d("Image", "$name: $uri")
}
}
性能优化
- 批量操作:用
applyBatch()代替多次单独操作 - 使用投影:只查询需要的列,避免
SELECT * - 异步查询:用
CursorLoader或协程避免阻塞 UI - 索引优化:底层数据库加索引
与 Room 结合
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
class UserProvider : ContentProvider() {
private lateinit var db: AppDatabase
override fun onCreate(): Boolean {
db = Room.databaseBuilder(context!!, AppDatabase::class.java, "users.db").build()
return true
}
override fun query(...): Cursor? {
// 把 Room 查询结果转成 Cursor
return MatrixCursor(...).apply {
db.userDao().getAll().forEach { ... }
}
}
}
小结
ContentProvider 是 Android 跨进程数据共享的标准方案:
- 通过 URI 统一访问接口
- 支持权限控制
- 可以监听数据变化
- 系统应用(联系人、相册、短信)都基于它实现
自己实现时注意线程安全、权限设计和性能优化。