Android Content Provider 详解

230 阅读4分钟

Content Provider 是 Android 四大核心组件之一,用于跨应用数据共享数据抽象化管理。它通过标准化的接口(基于 URI)封装数据访问逻辑,支持跨进程安全通信。本文从核心功能使用场景版本兼容代码实现注意事项五个维度全面解析。

一、核心功能与使用场景

功能使用场景
跨应用数据共享应用 A 公开数据(如用户配置、数据库)供应用 B 读取或修改。
统一数据访问接口封装复杂数据源(如 SQLite、文件、网络数据)为统一 API,简化调用方逻辑。
系统数据访问访问系统提供的 Content Provider(如联系人、媒体库、日历事件)。
数据安全控制通过权限机制限制其他应用对敏感数据的访问(如 android:readPermission)。

二、基础使用与代码示例

1、创建自定义 Content Provider

  • 步骤

    1. 继承 ContentProvider 并实现 onCreate()query()insert()update()delete() 和 getType()
    2. 在 AndroidManifest.xml 中声明 Provider。
  • 代码示例(Kotlin)

    class MyProvider : ContentProvider() {
        private lateinit var dbHelper: MyDbHelper
    
        override fun onCreate(): Boolean {
            dbHelper = MyDbHelper(context!!)
            return true
        }
    
        override fun query(
            uri: Uri, projection: Array<String>?, selection: String?,
            selectionArgs: Array<String>?, sortOrder: String?
        ): Cursor? {
            val db = dbHelper.readableDatabase
            val cursor = db.query("my_table", projection, selection, selectionArgs, null, null, sortOrder)
            cursor.setNotificationUri(context!!.contentResolver, uri)
            return cursor
        }
    
        // 实现其他方法(insert/update/delete/getType)...
    }
    
  • Manifest 声明

    <provider
        android:name=".MyProvider"
        android:authorities="com.example.myapp.provider"
        android:exported="true" <!-- 是否允许跨应用访问 -->
        android:readPermission="com.example.permission.READ_DATA" />
    

2、访问 Content Provider

  • 通过 ContentResolver

    val uri = Uri.parse("content://com.example.myapp.provider/my_table")
    val cursor = contentResolver.query(
        uri, 
        arrayOf("id", "name"),  // 查询的列
        "age > ?",              // WHERE 条件
        arrayOf("18"),          // 参数值
        "name DESC"             // 排序
    )
    cursor?.use {
        while (it.moveToNext()) {
            val id = it.getInt(it.getColumnIndex("id"))
            val name = it.getString(it.getColumnIndex("name"))
        }
    }
    

3、访问系统 Content Provider

  • 示例:读取联系人信息

    val contactsUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
    val projection = arrayOf(
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER
    )
    contentResolver.query(contactsUri, projection, null, null, null)?.use { cursor ->
        while (cursor.moveToNext()) {
            val name = cursor.getString(0)
            val phone = cursor.getString(1)
        }
    }
    

三、版本兼容性问题

版本关键变更适配方案
Android 6.0+运行时权限(如 READ_CONTACTS)需动态申请。调用系统 Provider 前检查权限: ContextCompat.checkSelfPermission() + ActivityResultLauncher
Android 7.0+禁止通过 file:// URI 直接共享文件,需使用 FileProvider使用 FileProvider 生成 content:// URI: 参考 FileProvider.getUriForFile()
Android 10+分区存储(Scoped Storage)限制直接访问外部存储文件路径。通过 MediaStore 或 Storage Access Framework 访问媒体文件。
Android 11+强制分区存储,requestLegacyExternalStorage 标志失效。使用 MediaStore 或申请 MANAGE_EXTERNAL_STORAGE 权限(需上架审核)。

四、注意事项与最佳实践

1、URI 设计规范

  • 路径匹配:使用 UriMatcher 管理 URI 路由。

    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI("com.example.provider", "books", BOOKS)
        addURI("com.example.provider", "books/#", BOOK_ID)
    }
    override fun getType(uri: Uri): String? {
        return when (uriMatcher.match(uri)) {
            BOOKS -> "vnd.android.cursor.dir/vnd.example.books"
            BOOK_ID -> "vnd.android.cursor.item/vnd.example.books"
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
    

2、数据安全

  • 权限控制:在 Manifest 中声明 android:permission 或细化到方法级别。

    <provider
        android:name=".SecureProvider"
        android:authorities="com.example.secure.provider"
        android:writePermission="com.example.permission.WRITE_DATA" />
    
  • 防止 SQL 注入:使用参数化查询,避免拼接 SQL 语句。

    // 错误方式(易受注入攻击)
    val selection = "id = $userInput"
    // 正确方式
    val selection = "id = ?"
    val selectionArgs = arrayOf(userInput)
    

3、性能优化

  • 异步查询:避免在主线程执行耗时操作,使用 CursorLoader 或协程。

    // 使用协程(Room + Kotlin Flow)
    @Dao
    interface BookDao {
        @Query("SELECT * FROM books")
        fun getAllBooks(): Flow<List<Book>>
    }
    
  • 批量操作:使用 ContentProviderOperation 提升批量写入效率。

    val operations = ArrayList<ContentProviderOperation>().apply {
        add(ContentProviderOperation.newInsert(uri)
            .withValue("title", "Book 1")
            .build())
        add(ContentProviderOperation.newInsert(uri)
            .withValue("title", "Book 2")
            .build())
    }
    contentResolver.applyBatch(authority, operations)
    

4、生命周期与资源释放

  • 关闭 Cursor:使用 Cursor.close() 或 use{} 扩展函数防止内存泄漏。
  • 注册观察者:通过 ContentResolver.registerContentObserver() 监听数据变化。
val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        // 数据变化时刷新 UI
    }
}
contentResolver.registerContentObserver(uri, true, observer)
// 在 onDestroy 中注销
override fun onDestroy() {
    contentResolver.unregisterContentObserver(observer)
}

五、典型使用场景

场景实现方案
跨应用共享数据库自定义 Content Provider 暴露 SQLite 表的 CRUD 操作。
文件共享使用 FileProvider 生成安全的 content:// URI,替代 file://
与 SyncAdapter 集成通过 Content Provider 同步远程服务器数据与本地存储(如邮件客户端)。
统一访问多数据源封装 REST API、数据库、文件系统为统一接口,简化客户端调用。

六、总结

需求推荐方案
简单数据共享自定义 Content Provider + 权限控制。
文件共享FileProvider(适配 Android 7.0+)。
访问系统数据(联系人、媒体)使用系统 Provider + 动态权限申请。
高性能数据操作CursorLoader + 异步处理(如协程、Room)。

通过合理设计 URI 结构、适配版本限制、优化查询性能并严格管控权限,可高效利用 Content Provider 实现安全、灵活的数据共享与管理。

更多分享

  1. 一文带你吃透Android中Service的种类和启动方式
  2. 一文带你吃透Android中显示Intent与隐式Intent的区别
  3. Android中Binder通信的优势以及与传统IPC的差异
  4. 一文带你吃透Android 中 AIDL 与 bindService 的核心区别
  5. Android 广播(Broadcast Receiver)详解