Skip to content

一、核心概念

1. 主要作用

  • 数据抽象:隐藏数据存储细节(如数据库表结构),通过 URI 和 ContentResolver 统一访问。
  • 跨应用共享:安全地允许其他应用读写数据(需权限控制)。
  • 数据监控:通过 ContentObserver 监听数据变化(如短信数据库更新)。

2. 关键组件

  • ContentProvider:数据提供者,实现 query()insert() 等方法。
  • ContentResolver:客户端通过它访问 ContentProvider。
  • Uri:唯一标识数据资源,格式为 content://authority/path/id

二、自定义 ContentProvider 实现步骤

1. 定义数据模型与 Contract 类

kotlin
// 定义数据表结构和 URI 常量
object BookContract {
    const val AUTHORITY = "com.example.provider.bookprovider"
    val CONTENT_URI = Uri.parse("content://$AUTHORITY/books")

    object BookEntry {
        const val TABLE_NAME = "books"
        const val _ID = "_id"
        const val COLUMN_TITLE = "title"
        const val COLUMN_AUTHOR = "author"
    }
}

2. 创建 ContentProvider 子类

kotlin
class BookProvider : ContentProvider() {
    private lateinit var dbHelper: SQLiteOpenHelper

    // Uri 匹配器,处理不同 URI 请求
    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI(BookContract.AUTHORITY, "books", BOOKS)
        addURI(BookContract.AUTHORITY, "books/#", BOOK_ID)
    }

    companion object {
        const val BOOKS = 1
        const val BOOK_ID = 2
    }

    override fun onCreate(): Boolean {
        dbHelper = BookDbHelper(context!!)
        return true
    }

    // 实现 CRUD 方法...
}

3. 实现 CRUD 操作

kotlin
override fun query(
    uri: Uri,
    projection: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
): Cursor? {
    val db = dbHelper.readableDatabase
    val cursor = when (uriMatcher.match(uri)) {
        BOOKS -> db.query(
            BookContract.BookEntry.TABLE_NAME,
            projection,
            selection,
            selectionArgs,
            null,
            null,
            sortOrder
        )
        BOOK_ID -> {
            val id = uri.lastPathSegment
            db.query(
                BookContract.BookEntry.TABLE_NAME,
                projection,
                "${BookContract.BookEntry._ID} = ?",
                arrayOf(id),
                null,
                null,
                sortOrder
            )
        }
        else -> throw IllegalArgumentException("Unknown URI: $uri")
    }
    cursor.setNotificationUri(context?.contentResolver, uri)
    return cursor
}

override fun insert(uri: Uri, values: ContentValues?): Uri? {
    val db = dbHelper.writableDatabase
    val id = when (uriMatcher.match(uri)) {
        BOOKS -> db.insert(BookContract.BookEntry.TABLE_NAME, null, values)
        else -> throw IllegalArgumentException("Unsupported URI: $uri")
    }
    context?.contentResolver?.notifyChange(uri, null)
    return ContentUris.withAppendedId(uri, id)
}

// 类似实现 update()、delete() 和 getType()

4. 注册 Provider

AndroidManifest.xml 中声明:

xml
<provider
    android:name=".BookProvider"
    android:authorities="com.example.provider.bookprovider"
    android:exported="true" <!-- 允许其他应用访问 -->
    android:readPermission="com.example.permission.READ_BOOKS"
    android:writePermission="com.example.permission.WRITE_BOOKS"/>

三、客户端通过 ContentResolver 访问数据

1. 查询数据

kotlin
val cursor = contentResolver.query(
    BookContract.CONTENT_URI,
    arrayOf(BookContract.BookEntry.COLUMN_TITLE, BookContract.BookEntry.COLUMN_AUTHOR),
    null,
    null,
    null
)

cursor?.use {
    while (it.moveToNext()) {
        val title = it.getString(it.getColumnIndex(BookContract.BookEntry.COLUMN_TITLE))
        val author = it.getString(it.getColumnIndex(BookContract.BookEntry.COLUMN_AUTHOR))
        Log.d("BookProvider", "Title: $title, Author: $author")
    }
}

2. 插入数据

kotlin
val values = ContentValues().apply {
    put(BookContract.BookEntry.COLUMN_TITLE, "Kotlin in Action")
    put(BookContract.BookEntry.COLUMN_AUTHOR, "Dmitry Jemerov")
}

val uri = contentResolver.insert(BookContract.CONTENT_URI, values)
Log.d("BookProvider", "Inserted URI: $uri")

四、权限控制

1. 声明自定义权限

在提供方应用的 AndroidManifest.xml

xml
<permission
    android:name="com.example.permission.READ_BOOKS"
    android:protectionLevel="signature" />

<permission
    android:name="com.example.permission.WRITE_BOOKS"
    android:protectionLevel="signature|dangerous" />

2. 客户端申请权限

在客户端应用的 AndroidManifest.xml

xml
<uses-permission android:name="com.example.permission.READ_BOOKS" />
<uses-permission android:name="com.example.permission.WRITE_BOOKS" />

五、高级功能

1. 使用 ContentObserver 监听数据变化

kotlin
// 注册观察者
val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        Log.d("BookProvider", "Data changed: $uri")
    }
}

contentResolver.registerContentObserver(
    BookContract.CONTENT_URI,
    true, // 监听所有子 URI
    observer
)

// 取消注册
contentResolver.unregisterContentObserver(observer)

2. 处理文件数据(流传输)

实现 openFile() 方法,用于共享文件:

kotlin
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
    val file = File(context?.filesDir, uri.lastPathSegment)
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
}

六、与 Room 数据库集成

1. 在 Room 中暴露 ContentProvider

kotlin
@Database(entities = [Book::class], version = 1)
abstract class BookDatabase : RoomDatabase() {
    abstract fun bookDao(): BookDao

    companion object {
        private var instance: BookDatabase? = null

        fun getInstance(context: Context): BookDatabase {
            return instance ?: synchronized(this) {
                instance ?: Room.databaseBuilder(
                    context,
                    BookDatabase::class.java, "book_database"
                ).build()
            }
        }
    }
}

// 在 ContentProvider 中通过 BookDatabase 操作数据

七、注意事项

  1. 线程安全

    • ContentProvider 方法默认在主线程调用,需自行处理耗时操作(如启动协程)。
    • 使用 @WorkerThread 注解提示调用者。
  2. URI 设计

    • 使用 UriMatcher 管理不同 URI 路径。
    • 支持批量操作 (applyBatch()) 提升效率。
  3. Android 11+ 包可见性

    • 若目标应用为 Android 11 (API 30) 及以上,需在客户端声明提供方包名:
      xml
      <queries>
          <package android:name="com.example.provider" />
      </queries>
  4. 性能优化

    • 使用 CursorLoader(或 LoaderManager)在后台加载数据。
    • 限制返回数据量(如分页查询)。

八、常见应用场景

  1. 系统数据访问:读取联系人、日历事件。
  2. 应用插件化:主应用与插件间数据交换。
  3. 数据备份/同步:通过自定义 Provider 暴露数据给备份服务。
  4. 跨应用文件共享:使用 FileProvider(ContentProvider 的子类)。

通过合理设计 ContentProvider,可实现安全高效的数据共享,同时遵循 Android 的最佳安全实践。