ContentProvider #

一、ContentProvider概述 #

ContentProvider(内容提供者)是Android四大组件之一,用于在不同应用之间共享数据。它提供了一套标准的接口,使得一个应用可以访问另一个应用的数据,同时保证数据的安全性。

1.1 ContentProvider的特点 #

  • 提供统一的数据访问接口
  • 支持跨进程数据共享
  • 封装数据存储细节
  • 提供权限控制机制

1.2 应用场景 #

  • 访问系统数据(联系人、短信、媒体库等)
  • 应用间数据共享
  • 数据同步

1.3 ContentProvider的注册 #

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <application>
        <provider
            android:name=".MyProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="false" />
    </application>

</manifest>

二、URI详解 #

2.1 URI结构 #

ContentProvider使用URI(统一资源标识符)来标识数据:

text
content://com.example.myapp.provider/users/1

结构:content://authority/path/id

- content://:固定前缀
- authority:提供者唯一标识
- path:数据路径(表名)
- id:具体记录ID(可选)

2.2 URI示例 #

text
content://com.example.myapp.provider/users      # 所有用户
content://com.example.myapp.provider/users/1    # ID为1的用户
content://com.example.myapp.provider/users/#    # 任意ID的用户
content://contacts/people                        # 系统联系人
content://media/external/images/media           # 外部存储图片

2.3 UriMatcher #

kotlin
class MyProvider : ContentProvider() {
    
    companion object {
        const val AUTHORITY = "com.example.myapp.provider"
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY")
        
        const val USERS = 1
        const val USER_ID = 2
    }
    
    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI(AUTHORITY, "users", USERS)
        addURI(AUTHORITY, "users/#", USER_ID)
    }
    
    private fun matchUri(uri: Uri): Int {
        return uriMatcher.match(uri)
    }
}

三、创建ContentProvider #

3.1 实现ContentProvider #

kotlin
class UserProvider : ContentProvider() {
    
    companion object {
        const val AUTHORITY = "com.example.myapp.provider"
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/users")
        
        const val USERS = 1
        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 dbHelper: DatabaseHelper
    
    override fun onCreate(): Boolean {
        dbHelper = DatabaseHelper(context!!)
        return true
    }
    
    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        val db = dbHelper.readableDatabase
        val cursor = when (uriMatcher.match(uri)) {
            USERS -> {
                db.query(
                    DatabaseHelper.TABLE_USERS,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder
                )
            }
            USER_ID -> {
                val id = ContentUris.parseId(uri)
                db.query(
                    DatabaseHelper.TABLE_USERS,
                    projection,
                    "_id = ?",
                    arrayOf(id.toString()),
                    null,
                    null,
                    sortOrder
                )
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
        
        cursor.setNotificationUri(context?.contentResolver, uri)
        return cursor
    }
    
    override fun getType(uri: Uri): String {
        return when (uriMatcher.match(uri)) {
            USERS -> "vnd.android.cursor.dir/vnd.example.users"
            USER_ID -> "vnd.android.cursor.item/vnd.example.users"
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
    
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val db = dbHelper.writableDatabase
        
        return when (uriMatcher.match(uri)) {
            USERS -> {
                val id = db.insert(DatabaseHelper.TABLE_USERS, null, values)
                if (id > 0) {
                    val newUri = ContentUris.withAppendedId(uri, id)
                    context?.contentResolver?.notifyChange(newUri, null)
                    newUri
                } else {
                    null
                }
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
    
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        val db = dbHelper.writableDatabase
        val count = when (uriMatcher.match(uri)) {
            USERS -> {
                db.delete(DatabaseHelper.TABLE_USERS, selection, selectionArgs)
            }
            USER_ID -> {
                val id = ContentUris.parseId(uri)
                db.delete(DatabaseHelper.TABLE_USERS, "_id = ?", arrayOf(id.toString()))
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
        
        if (count > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        
        return count
    }
    
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        val db = dbHelper.writableDatabase
        val count = when (uriMatcher.match(uri)) {
            USERS -> {
                db.update(DatabaseHelper.TABLE_USERS, values, selection, selectionArgs)
            }
            USER_ID -> {
                val id = ContentUris.parseId(uri)
                db.update(DatabaseHelper.TABLE_USERS, values, "_id = ?", arrayOf(id.toString()))
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
        
        if (count > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        
        return count
    }
}

3.2 MIME类型 #

类型 格式 说明
多条记录 vnd.android.cursor.dir/vnd.company.type 如:所有用户
单条记录 vnd.android.cursor.item/vnd.company.type 如:单个用户

四、使用ContentProvider #

4.1 ContentResolver #

kotlin
class MainActivity : AppCompatActivity() {
    
    private val contentResolver = contentResolver
    
    fun queryUsers() {
        val uri = Uri.parse("content://com.example.myapp.provider/users")
        
        val cursor = contentResolver.query(
            uri,
            arrayOf("_id", "name", "email"),
            null,
            null,
            "name ASC"
        )
        
        cursor?.use {
            while (it.moveToNext()) {
                val id = it.getLong(it.getColumnIndexOrThrow("_id"))
                val name = it.getString(it.getColumnIndexOrThrow("name"))
                val email = it.getString(it.getColumnIndexOrThrow("email"))
                Log.d("User", "ID: $id, Name: $name, Email: $email")
            }
        }
    }
    
    fun insertUser(name: String, email: String) {
        val uri = Uri.parse("content://com.example.myapp.provider/users")
        
        val values = ContentValues().apply {
            put("name", name)
            put("email", email)
        }
        
        val newUri = contentResolver.insert(uri, values)
        Log.d("Insert", "New URI: $newUri")
    }
    
    fun updateUser(id: Long, name: String) {
        val uri = ContentUris.withAppendedId(
            Uri.parse("content://com.example.myapp.provider/users"),
            id
        )
        
        val values = ContentValues().apply {
            put("name", name)
        }
        
        val count = contentResolver.update(uri, values, null, null)
        Log.d("Update", "Updated $count rows")
    }
    
    fun deleteUser(id: Long) {
        val uri = ContentUris.withAppendedId(
            Uri.parse("content://com.example.myapp.provider/users"),
            id
        )
        
        val count = contentResolver.delete(uri, null, null)
        Log.d("Delete", "Deleted $count rows")
    }
}

4.2 使用ContentObserver监听数据变化 #

kotlin
class MainActivity : AppCompatActivity() {
    
    private val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
        override fun onChange(selfChange: Boolean) {
            super.onChange(selfChange)
            // 数据变化时刷新UI
            refreshData()
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 注册观察者
        val uri = Uri.parse("content://com.example.myapp.provider/users")
        contentResolver.registerContentObserver(uri, true, observer)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // 注销观察者
        contentResolver.unregisterContentObserver(observer)
    }
}

五、访问系统ContentProvider #

5.1 读取联系人 #

kotlin
fun readContacts() {
    val cursor = contentResolver.query(
        ContactsContract.Contacts.CONTENT_URI,
        arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME
        ),
        null,
        null,
        null
    )
    
    cursor?.use {
        while (it.moveToNext()) {
            val id = it.getLong(it.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
            val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME))
            
            // 查询电话号码
            val phoneCursor = contentResolver.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
                "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
                arrayOf(id.toString()),
                null
            )
            
            phoneCursor?.use { phone ->
                while (phone.moveToNext()) {
                    val number = phone.getString(phone.getColumnIndexOrThrow(
                        ContactsContract.CommonDataKinds.Phone.NUMBER
                    ))
                    Log.d("Contact", "Name: $name, Phone: $number")
                }
            }
        }
    }
}

5.2 读取媒体库图片 #

kotlin
fun readImages() {
    val projection = arrayOf(
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.DISPLAY_NAME,
        MediaStore.Images.Media.DATE_ADDED
    )
    
    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(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
            val name = it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
            val uri = ContentUris.withAppendedId(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                id
            )
            Log.d("Image", "Name: $name, URI: $uri")
        }
    }
}

六、权限控制 #

6.1 声明权限 #

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <!-- 声明权限 -->
    <permission
        android:name="com.example.myapp.provider.READ"
        android:label="读取数据"
        android:protectionLevel="normal" />
    
    <permission
        android:name="com.example.myapp.provider.WRITE"
        android:label="写入数据"
        android:protectionLevel="dangerous" />

    <application>
        <provider
            android:name=".MyProvider"
            android:authorities="com.example.myapp.provider"
            android:readPermission="com.example.myapp.provider.READ"
            android:writePermission="com.example.myapp.provider.WRITE"
            android:exported="true" />
    </application>

</manifest>

6.2 使用权限 #

xml
<manifest>
    <uses-permission android:name="com.example.myapp.provider.READ" />
    <uses-permission android:name="com.example.myapp.provider.WRITE" />
</manifest>

6.3 临时权限授予 #

xml
<provider
    android:name=".MyProvider"
    android:authorities="com.example.myapp.provider"
    android:grantUriPermissions="true"
    android:exported="false" />
kotlin
// 临时授权
val uri = Uri.parse("content://com.example.myapp.provider/users/1")
grantUriPermission("com.example.otherapp", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)

七、ContentProvider最佳实践 #

7.1 使用Room + ContentProvider #

kotlin
class UserProvider : ContentProvider() {
    
    private lateinit var database: AppDatabase
    
    override fun onCreate(): Boolean {
        database = Room.databaseBuilder(
            context!!,
            AppDatabase::class.java,
            "app.db"
        ).build()
        return true
    }
    
    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        return when (uriMatcher.match(uri)) {
            USERS -> {
                database.userDao().getAllCursor()
            }
            else -> null
        }
    }
}

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllCursor(): Cursor
}

7.2 使用协程处理异步操作 #

kotlin
class UserProvider : ContentProvider() {
    
    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        return runBlocking {
            withContext(Dispatchers.IO) {
                // 异步查询
                database.userDao().getAllCursor()
            }
        }
    }
}

八、总结 #

本章详细介绍了ContentProvider:

  1. ContentProvider的基本概念
  2. URI的结构和使用
  3. 创建自定义ContentProvider
  4. 使用ContentResolver访问数据
  5. 访问系统ContentProvider
  6. 权限控制机制
  7. 最佳实践

ContentProvider是Android跨应用数据共享的核心机制,合理使用可以实现安全、高效的数据共享。

最后更新:2026-03-26