ContentProvider 跨进程数据共享实战

简介: ContentProvider是Android跨进程数据共享标准方案:通过URI统一访问接口,支持CRUD操作、权限控制与数据变更监听。适用于共享联系人/相册等系统数据,或自建安全的数据服务,需注意线程安全与性能优化。

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

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

相关文章
人工智能 缓存 前端开发
11691 58
人工智能 JavaScript 开发工具
4660 17
开发工具 Swift git
1881 6
Web App开发 人工智能 API
1157 1
人工智能 Java BI
1292 1
人工智能 JavaScript 测试技术
2121 2
人工智能 JavaScript 测试技术
1079 4
缓存 JavaScript Shell
2052 3