ContentProvider 跨进程数据共享实战

为什么需要 ContentProvider

Android 应用间默认是隔离的,每个 App 运行在独立进程中。当你需要:

  • 把自己的数据共享给其他 App(联系人、相册)
  • 访问系统或第三方 App 的数据(通讯录、短信)
  • 统一数据访问接口,隐藏底层实现(SQLite / 文件 / 网络)

就需要 ContentProvider 作为跨进程数据通道。

核心概念

URI 定位

bash 复制代码
content://com.example.app.provider/users/1
         ↓                          ↓      ↓
    authority                    table   id
  • authority:全局唯一标识符,通常用包名
  • path:指定数据类型(表名)
  • id:可选,指定具体记录

CRUD 方法

scss 复制代码
query()   // 查询
insert()  // 插入
update()  // 更新
delete()  // 删除
getType() // 返回 MIME 类型

实战:创建自己的 Provider

1. 定义 Provider 类

kotlin 复制代码
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

ini 复制代码
<provider
    android:name=".UserProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:permission="com.example.READ_USERS" />
  • exported="true":允许其他应用访问
  • permission:可选,限制访问权限

3. 其他应用访问

kotlin 复制代码
// 查询所有用户
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
)

监听数据变化

kotlin 复制代码
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. 定义自定义权限

ini 复制代码
<permission
    android:name="com.example.READ_USERS"
    android:protectionLevel="normal" />

2. 在 Provider 中声明

ini 复制代码
<provider
    android:readPermission="com.example.READ_USERS"
    android:writePermission="com.example.WRITE_USERS" />

3. 调用方申请权限

ini 复制代码
<uses-permission android:name="com.example.READ_USERS" />

常见场景

访问系统联系人

java 复制代码
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" />

访问相册

kotlin 复制代码
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 结合

kotlin 复制代码
@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 统一访问接口
  • 支持权限控制
  • 可以监听数据变化
  • 系统应用(联系人、相册、短信)都基于它实现

自己实现时注意线程安全、权限设计和性能优化。

相关推荐
计算机魔术师1 小时前
飞书与豆包合并后首款Agent产品"豆包工作"发布
前端
天空之城--2 小时前
Android输入法子系统深度解析
android
独孤九剑打醒他2 小时前
从“铁块一直在辐射电磁波“到EUV光源:微波等离子体MPP架构全链路推演
前端·架构·硬件工程
光电的一只菜鸡2 小时前
高通tuning中eis需要调什么
java·开发语言·前端
quweiie2 小时前
腾讯云视频点播-web上传视频
前端·音视频·腾讯云·上传视频·腾讯云视频点播
API快乐传递者3 小时前
1688 跨境电商 API 接口实战指南:从寻源到代采的全链路技术方案
java·前端·数据库
马可家的菠萝3 小时前
收藏不是终点:一个真正有用的个人知识库,至少要完成“收集 → 理解 → 行动”
前端·后端·架构
天空之城--3 小时前
Android全链路性能优化:从原理到实践的终极指南
android·性能优化
明月_清风3 小时前
看完 DSH 文档后,我总结了这 7 个关键点
前端·后端·deepseek