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

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

相关推荐
moshuying19 分钟前
扩散模型开始写代码、开网页了:350 亿参数,速度比 Jev 快一倍
前端·算法
qq_4017004135 分钟前
100 个 IoT 小项目,给 ESP32 入门补上实操
前端·网络·stm32
输出输入1 小时前
学习前端主流书单推荐
前端
anyup1 小时前
uView Pro X 1.0.0 发布:全面支持 uni-app x 蒸汽模式,80+ 组件
前端·uni-app·ai编程
apollo_qwe1 小时前
大模型 API 接通了,AI 聊天页面为什么还是不好用?
前端·vue.js·人工智能
爱勇宝1 小时前
你可能成不了技术大佬,然后呢?
前端·后端·程序员
疯华整猫2 小时前
从 MVP 到 MVI,从 Java 到 Kotlin:老项目升级的避坑指南
前端
徐小夕@趣谈前端2 小时前
Web 端 OFD 编辑器踩坑记:从“只能预览”到“在线改”
前端·编辑器
前端小万2 小时前
想做个人副业别乱买服务器!我的 5 个月真实开销分享
前端·后端·github
TimeFine3 小时前
AI 编码工作流的设计
android·ai编程