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

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

相关文章
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
14179 1
|
3月前
|
存储 自然语言处理 测试技术
[鸿蒙从零到一] HarmonyOS 通知与提醒实战:消息发布、点击跳转与定时触达
本文详解鸿蒙HarmonyOS通知与提醒实战:区分NotificationKit(即时通知)与ReminderAgent(定时提醒),涵盖权限申请、通知槽管理、点击跳转、进度更新、定时触发、ID去重、错误处理及真机测试要点,助开发者构建稳定可靠的生产级消息触达能力
458 0
|
3月前
|
存储 缓存 API
[鸿蒙从零到一] HarmonyOS 后台任务与定时能力实战:短时任务、长时任务与延迟调度
本文系统讲解HarmonyOS后台任务实战,涵盖短时任务(收尾保护)、长时任务(用户可感知持续运行)、延迟任务(Work Scheduler按条件调度)及代理提醒(准时通知),结合ArkTS代码、权限配置、异常处理与测试要点,助开发者构建稳定、合规、低耗的后台能力
261 0
|
1月前
|
JSON Shell API
[鸿蒙从零到一] HarmonyOS 冷启动瀑布图分析与关键路径裁剪实战
本文详解HarmonyOS冷启动优化:先用HiTrace+DevEco Profiler获取可信瀑布图,精准识别“点击→首帧”关键路径;再通过延迟、并行、裁剪、预置四步法裁剪耗时,实测冷启动时间降低41.5%。强调“无图不优化”,拒绝盲目删初始化
112 1
|
29天前
|
Java 测试技术 API
Android test全景:从单元 test到 UI 自动化的完整实践
本文系统讲解Android测试体系建设:涵盖JUnit/Truth单元测试、Mockito依赖隔离、Robolectric本地Android测试、Espresso UI测试及UI Automator跨应用测试,并集成JaCoCo覆盖率与GitHub Actions持续集成,助力打造高质量、可维护的Android应用。
140 0
|
1月前
|
缓存 监控 测试技术
Android 多渠道打包与 Gradle 构建优化实战
本文详解Android多渠道打包与Gradle构建优化实战,涵盖`productFlavors`、`buildTypes`、`flavorDimensions`核心配置,渠道信息注入(BuildConfig/Manifest/资源)、四大加速策略(缓存/并行/模块化/脚本优化),并提供从单构建到多维矩阵的演进案例,助你提升构建效率与工程化水平
155 0
|
1月前
|
缓存 API 数据库
[鸿蒙从零到一] relationalStore 高级实战:事务、版本迁移与数据库加密
本文深入解析鸿蒙relationalStore三大高级能力:事务(大幅提升批量写入性能)、版本迁移(保障跨版本升级数据安全)与数据库加密(系统级落盘加密)。结合WAL机制剖析、实测数据对比及可复用封装方案,助开发者构建高可靠、易维护的数据层。
117 0
|
1月前
|
API Android开发 容器
StateFlow 与 SharedFlow 的边界:状态与事件的正确建模
本文剖析 Android 开发中 StateFlow 与 SharedFlow 的核心语义差异:StateFlow 适用于可重放的**状态**(如加载态、数据),SharedFlow 适用于一次性消费的**事件**(如跳转、Toast)。混淆二者将导致重复跳转或事件丢失。文章结合真实 Bug,厘清边界,并给出 Channel、状态化等可靠替代方案。
89 0
|
1月前
|
网络协议 API 网络安全
HarmonyOS WebSocket 实战:断线重连、心跳保活与连接状态机设计
本文详解鸿蒙WebSocket高可用实践:直击弱网断连无感知、重连风暴、消息丢失三大痛点,提出“心跳保活+指数退避重连+显式状态机”三层架构,并提供完整ArkTS可运行代码,助开发者构建生产级实时通信能力。
80 0
|
2月前
|
缓存 JSON 编译器
[鸿蒙从零到一] ArkTS Sendable 数据模型与跨线程共享实战
Sendable 是 ArkTS 实现跨线程零拷贝共享的核心机制:通过编译期类型校验与 Shared Heap 运行时支持,使对象真正被多线程共用。需显式标注、字段限于基础类型或 Sendable 容器(如 collections.Map),并发写操作须配合 AsyncLock 保障一致性。适用于大数据只读或可变状态共享场景,小数据仍推荐结构化克隆。
138 0

热门文章

最新文章