安卓开发-ContentProvider组件基础学习

最近系统学习了安卓开发中的四大组件之一的ContentProvider,写一篇博客记录一下,若有错误,欢迎大佬们指正~

ContentProvider 是 Android 中一个非常核心且强大的组件,它主要用于在不同应用程序之间共享数据,并提供了一套标准的、安全的接口来访问和修改这些数据。

可以把它想象成一个公共的数据仓库,任何有权限的应用都可以通过标准化的 “API” 来访问其中的数据。

类似于前后端数据交互,前端通过特定的url链接,向后端发生不同的请求,获取数据,而在安卓开发中则是使用ContentProvider,通过特定URI获取数据。

安卓开发中:
	使用ContentProvider来解析URI,判断进行什么操作,返回什么数据
	使用ContentResolver向ContentProvider传递URI,用于获取数据
	URI:类似于前后端交互中的URL,唯一标识数据源
	dbHepler:数据源操作对象,ContentProvider通过其操作数据源,进行数据操作(CRUD)
与前后端交互不同的是:
	ContentProvider和ContentResolver有着update,inser,delete,query等方法,表示不同的操作,而不是	 将具体的操作写进URI中
具体流程如下:
	客户端 (应用 B)
|
| 1. 构造精细的 URI (例如: content://authority/students/101)
| 2. 检查并请求必要的权限 (在 Manifest 中声明)
| 3. 通过 ContentResolver 调用 query() / insert() / update() / delete()
|
|----> Android 系统 (Binder IPC)
|
| 4. 根据 URI 的 Authority 找到并激活应用 A 的 MyCustomProvider
| 5. 在 Provider 的 Binder 线程中执行对应的方法 (query, etc.)
|    |
|    |---> Provider 内部
|    |     |
|    |     | a. 使用 UriMatcher 解析 URI,获取操作类型和数据 ID
|    |     | b. 执行线程安全的数据库操作 (通过 DbHelper)
|    |     | c. (可选) 操作成功后,调用 notifyChange()
|    |     | d. 返回 Cursor 或结果
|    |
| 6. 将结果 (Cursor) 通过 Binder 返回
|
| 7. 客户端在回调/主线程中接收 Cursor
| 8. 客户端处理 Cursor 数据并更新 UI
| 9. 客户端负责关闭 Cursor (最好用 CursorLoader)
| 10. (可选) 客户端通过 ContentObserver 监听数据变化,重复步骤 3-9

个人感觉这样设计,是因为需要进行跨进程访问数据

核心作用

  • 跨应用数据共享
  • 数据封装与抽象
  • 数据安全
  • 统一的数据访问方式

ContentProvider 的核心组件与工作流程

URI (Uniform Resource Identifier)
  • 作用:URI 是 ContentProvider 的 “地址”,用于唯一标识 ContentProvider 管理的某类数据。
  • 格式content://authority/path/optional_id
  • 示例
    • content://com.example.myapp.provider/students
    • content://com.example.myapp.provider/students/101
ContentProvider 类

这是需要创建的核心类,它继承自 android.content.ContentProvider

import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri

class MyCustomProvider : ContentProvider() {

    // 通常在这里定义 authority 和一些路径常量
    companion object {
        const val AUTHORITY = "com.example.myapp.provider"
        val BASE_URI: Uri = Uri.parse("content://$AUTHORITY")
        const val PATH_STUDENTS = "students"
        // 可以使用 UriMatcher 来帮助解析不同的 URI
        // private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
        // init { uriMatcher.addURI(AUTHORITY, PATH_STUDENTS, STUDENTS) }
    }

    /**
     * ContentProvider 创建时调用。
     * 通常在这里进行初始化,比如创建或打开数据库。
     */
    override fun onCreate(): Boolean {
        // 初始化代码,例如获取数据库实例
        // dbHelper = MyDatabaseHelper(context)
        return true
    }

    /**
     * 从 ContentProvider 中查询数据。
     * @param uri 要查询的 URI。
     * @param projection 要返回的列名数组。
     * @param selection 查询条件子句。
     * @param selectionArgs 查询条件参数。
     * @param sortOrder 结果排序方式。
     * @return 一个 Cursor 对象,包含查询结果。
     */
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        // 根据 uri 判断要查询哪个表/集合
        // 使用 uriMatcher.match(uri) 来获取匹配码
        // ...
        // 执行数据库查询
        // val db = dbHelper.readableDatabase
        // val cursor = db.query(...)
        // return cursor
        return null // 返回实际的 Cursor 对象
    }

    /**
     * 返回指定 URI 所代表的数据的 MIME 类型。
     */
    override fun getType(uri: Uri): String? {
        // 根据 URI 返回对应的 MIME 类型
        // 例如: vnd.android.cursor.dir/vnd.com.example.myapp.students
        // 或: vnd.android.cursor.item/vnd.com.example.myapp.student
        return null
    }

    /**
     * 向 ContentProvider 中插入一条新记录。
     * @param uri 要插入数据的目标 URI。
     * @param values 包含要插入数据的 ContentValues 对象。
     * @return 指向新插入记录的 URI。
     */
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 执行数据库插入
        // val db = dbHelper.writableDatabase
        // val newRowId = db.insert(...)
        // if (newRowId > 0) {
        //     val newUri = ContentUris.withAppendedId(BASE_URI.buildUpon().appendPath(PATH_STUDENTS).build(), newRowId)
        //     context?.contentResolver?.notifyChange(newUri, null) // 通知数据变化
        //     return newUri
        // }
        return null // 返回新记录的 URI
    }

    /**
     * 从 ContentProvider 中删除记录。
     * @param uri 要删除数据的 URI。
     * @param selection 删除条件子句。
     * @param selectionArgs 删除条件参数。
     * @return 被删除的记录数量。
     */
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        // 执行数据库删除
        // val db = dbHelper.writableDatabase
        // val rowsDeleted = db.delete(...)
        // if (rowsDeleted > 0) {
        //     context?.contentResolver?.notifyChange(uri, null)
        // }
        // return rowsDeleted
        return 0 // 返回实际删除的行数
    }

    /**
     * 更新 ContentProvider 中已有的记录。
     * @param uri 要更新数据的 URI。
     * @param values 包含要更新数据的 ContentValues 对象。
     * @param selection 更新条件子句。
     * @param selectionArgs 更新条件参数。
     * @return 被更新的记录数量。
     */
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        // 执行数据库更新
        // val db = dbHelper.writableDatabase
        // val rowsUpdated = db.update(...)
        // if (rowsUpdated > 0) {
        //     context?.contentResolver?.notifyChange(uri, null)
        // }
        // return rowsUpdated
        return 0 // 返回实际更新的行数
    }
}
ContentResolver 类

ContentResolver 是应用程序用来与 ContentProvider 进行交互的 “客户端”。在 Kotlin 中,获取和使用它非常方便。不需要直接实例化 ContentProvider,而是通过 context.contentResolver 获取一个 ContentResolver 对象。

// context 是上下文
val resolver = context.contentResolver
// activity 中使用
val resolver = this.contentResolver

为什么不直接调用 ContentProvider?

为了解耦和安全。ContentResolver 提供了一个统一的、进程间通信(IPC)安全的代理层。

ContentValues 类

一个简单的键值对容器,用于在 insertupdate 操作中传递数据。

它就像是一个通用的数据载体,避免了直接暴露数据库的 Cursor 给客户端进行写入操作。

示例:

// 方式一 
val values = ContentValues().apply {
    put("name", "张三")
    put("age", 25)
    put("major", "计算机科学")
}

// 方式二
val valuesKotlin = ContentValues().apply {
    put("name", "李四")
    put("age", 22)
    put("major", "软件工程")
}

// 然后利用contentResolver传递数据到对应URI的contentProvider
contentResolver.insert(URI,valuesKotlin)
Cursor 类

查询操作 (query) 的返回结果。它提供了一个移动的指针,指向查询结果集的行和列,可以通过遍历它来读取数据。

示例:

// 从contentProvider返回一个cursor用于遍历数据, 使用 use 函数进行查询,自动管理 Cursor 的生命周期
resolver.query(uri, null, null, null, null)?.use { cursor ->
    // 检查 Cursor 是否为空并移动到第一行
    if (cursor.moveToFirst()) {
        // 遍历 Cursor
        do {
            // 获取列索引
            val idIndex = cursor.getColumnIndexOrThrow("_id")
            val nameIndex = cursor.getColumnIndexOrThrow("name")
            val ageIndex = cursor.getColumnIndexOrThrow("age")
            // 获取数据
            val id = cursor.getLong(idIndex)
            val name = cursor.getString(nameIndex)
            val age = cursor.getInt(ageIndex)

            // 处理数据,例如添加到列表中
            // Log.d("TAG", "ID: $id, Name: $name, Age: $age")

        } while (cursor.moveToNext())
    }
} // Cursor 在这里会被自动 close()

注:getColumnIndexOrThrow 会在列名不存在时抛出异常,比 getColumnIndex 更安全,因为后者会返回 -1,容易导致后续 get\* 方法出错。


使用 ContentProvider 的基本步骤

创建对应的数据源和数据源帮助类,用于底层数据的操作
编写自定义的contentProvider类,自定义对应的URI,并进行注册,重写方法,调用数据源帮助类进行数据操作,
获取contentResolver对象,传入指定的URI,通过对应的contentProvider进行数据操作
创建数据源对象,用于数据的操作的执行

数据类

data class Student(
    val id: Long,
    val name: String,
    val gradeId: Int
)

这里使用Sqlite数据库的Student表作为数据源

package com.example.service.provider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
 * 数据库帮助类 (SQLiteOpenHelper) 的具体实现。
 * 这个类的主要职责是:
 * 1. 管理数据库的创建(onCreate)。
 * 2. 管理数据库版本的升级(onUpgrade)。
 * 3. 提供对数据库版本号、表名、列名等常量的集中定义。
 * 使用 SQLiteOpenHelper 是 Android 中操作 SQLite 数据库的最佳实践,
 * 它能自动处理数据库的打开、创建和升级逻辑,避免了手动管理数据库文件的复杂性。
 */
class StudentDbHelper(context: Context) : SQLiteOpenHelper(
    context,         // 上下文,用于访问应用的资源和路径
    DATABASE_NAME,   // 数据库文件的名称
    null,            // 游标工厂,通常为 null 使用默认实现
    DATABASE_VERSION // 数据库的版本号
) {
    /**
     *  companion object (伴生对象) 用于存放静态常量和静态方法。
     *  将数据库结构相关的常量(如数据库名、表名、列名)集中定义在这里,
     *  可以提高代码的可维护性,并避免在代码中使用魔法字符串(Magic Strings)。
     */
    companion object {
        // 数据库文件名称
        private const val DATABASE_NAME = "StudentsDatabase.db"
        // 数据库版本号。当你需要修改数据库结构时(如增加表、修改列),
        // 必须增加这个版本号,系统会自动调用 onUpgrade 方法。
        private const val DATABASE_VERSION = 1
        // 数据表名称:存储学生信息
        const val TABLE_STUDENTS = "students"
        // 表中的列名
        const val COLUMN_ID = "_id"      // 主键ID
        const val COLUMN_NAME = "name"   // 学生姓名
        const val COLUMN_GRADE = "grade" // 学生年级
    }

    /**
     * 当数据库第一次被创建时,这个方法会被调用。
     * 通常在这里执行 CREATE TABLE 语句来初始化数据库的结构。
     *
     * @param db 可写入的数据库实例。
     */
    override fun onCreate(db: SQLiteDatabase?) {
        // 使用 trimIndent() 可以让多行字符串的缩进更整洁,是Kotlin的一个实用特性。
        val createTable = """
            CREATE TABLE $TABLE_STUDENTS(
                $COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT,
                $COLUMN_NAME TEXT NOT NULL,
                $COLUMN_GRADE INTEGER NOT NULL
            )
        """.trimIndent()

        // 执行SQL语句创建表。
        // 注意:db 参数可能为 null,因此使用了安全调用 ?.
        db?.execSQL(createTable)
    }

    /**
     * 当数据库版本号增加时(即 DATABASE_VERSION 变大),系统会调用此方法。
     * 用于处理数据库结构的升级,例如添加新表、修改现有表的结构(增加列、修改列类型等)。
     * @param db 可写入的数据库实例。
     * @param oldVersion 旧的数据库版本号。
     * @param newVersion 新的数据库版本号。
     */
    override fun onUpgrade(
        db: SQLiteDatabase?,
        oldVersion: Int,
        newVersion: Int
    ) {
        // 首先检查数据库实例是否为 null
        if (db == null) return
        // 示例:如何处理从版本1升级到版本2
        if (oldVersion < 2) {
            // 假设要在版本2中为 students 表增加一个 email 列
            // ALTER TABLE 语句用于修改表结构
            db.execSQL("ALTER TABLE $TABLE_STUDENTS ADD COLUMN email TEXT")
        }
        // 示例:如何处理从版本2升级到版本3
        if (oldVersion < 3) {
            // 假设要在版本3中再增加一个 phone 列
            db.execSQL("ALTER TABLE $TABLE_STUDENTS ADD COLUMN phone TEXT")
        }
    }
}
创建的 ContentProvider (作为数据提供方)

创建一个 Kotlin 类,继承自 ContentProvider,并重写上述关键方法。

其中对于访问的Uri,规定如下

content://xxxx/students 		--没有id,则对整张表进行操作
content://xxxx/students/#id		--拥有id,则对指定id的数据进行操作

ContentProvider的创建:

伴生对象:存放所有与 URI 相关的常量和 UriMatcher
	-- AUTHORITY:   ContentProvider 的唯一标识,通常是应用的包名 + ".provider"
	-- PATH_XXXX:   数据源的路径(例如:表的路径,PATH_STUDENT就是,student这个表的)
		-- 而具体使用的数据库,看具体使用的数据源对象
	-- CONTENT_URI: 访问的URI,格式为: content://<authority>/<path>
		-- 在本文中指的是访问符合<authority>的应用中的<path>表
	-- uriMatcher:  通过addURI方法指定匹配的规则
		-- 第一个参数:authority
        -- 第二个参数:path,可以包含通配符 # (匹配数字) 和 * (匹配任意文本)
        -- 第三个参数:匹配成功后的返回码	
        -- 当匹配成功后,根据返回的返回码,进入不同的when分支执行不同的操作
onCreate():初始化,初始化对应的数据源对象
接受ContentResolver要使用的指定方法(update...),并解析Uri,进入不同的when分支,执行不同的操作
返回结果(query返回cursor对象,其余返回影响的条数)

注意返回的cursor对象不要在ContentProvider中关闭了再返回,否则会影响ContentResolver的使用

package com.example.service.provider;
import android.annotation.SuppressLint;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import androidx.core.net.toUri;

/**
 * 一个自定义的 ContentProvider,用于管理 "学生" 数据的跨进程访问。
 *
 * 这个类是应用的数据访问层,它对外提供了标准化的 CRUD (Create, Read, Update, Delete) 接口,
 * 隐藏了底层 SQLite 数据库的具体实现细节。其他应用(或本应用内的其他组件)可以通过 ContentResolver
 * 来访问这里提供的数据。
 */
class StudentProvider : ContentProvider() {
    /**
     * 伴生对象,存放所有与 URI 相关的常量和 UriMatcher。
     */
    companion object {
        // 1.  authorities:ContentProvider 的唯一标识,通常是应用的包名 + ".provider"
        //     客户端通过这个标识来找到的 Provider。
        private const val AUTHORITY = "com.example.service.provider"
        // 2.  path:代表数据源的路径,这里的学生表。
        private const val PATH_STUDENT = "students"
        // 3.  完整的 CONTENT_URI:用于访问学生表的基础 URI。
        //     格式为:content://<authority>/<path>
        val CONTENT_URI: Uri = "content://$AUTHORITY/$PATH_STUDENT".toUri()
        // 4.  UriMatcher 的匹配码:用于在 switch-case 中识别不同的 URI 模式。
        private const val STUDENTS = 1       // 匹配整个学生表
        private const val STUDENT_ID = 2     // 匹配表中的单条记录 (例如 /students/123)
        /**
         * 5.  UriMatcher:一个帮助类,用于将 incoming 的 URI 与定义的模式进行匹配。
         *     构造函数中的 UriMatcher.NO_MATCH 是一个默认值,表示不匹配任何模式。
         */
        private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            // 添加 URI 模式:
            // - 第一个参数:authority
            // - 第二个参数:path,可以包含通配符 # (匹配数字) 和 * (匹配任意文本)
            // - 第三个参数:匹配成功后的返回码
            addURI(AUTHORITY, PATH_STUDENT, STUDENTS)          // 匹配 content://.../students
            addURI(AUTHORITY, "$PATH_STUDENT/#", STUDENT_ID)   // 匹配 content://.../students/123
        }
    }
    // 数据库帮助类实例,用于获取数据库对象
    private lateinit var dbHelper: StudentDbHelper
    // 数据库实例,在各个方法中根据需要获取可读或可写数据库
    private lateinit var db: SQLiteDatabase
    /**
     * 【核心方法】实现删除数据的功能。
     * @param uri 要删除数据的 URI。
     * @param selection SQL 的 WHERE 子句(不带 WHERE 关键字)。
     * @param selectionArgs 用于替换 selection 中 "?" 的参数数组。
     * @return 被删除的行数。
     */
    override fun delete(
        uri: Uri,
        selection: String?,
        selectionArgs: Array<String?>?
    ): Int {
        // 获取可写数据库
        db = dbHelper.writableDatabase
        var rowDeleted: Int = 0
        // 使用 uriMatcher 匹配 URI,确定是删除整个表还是单条记录
        when (uriMatcher.match(uri)) {
            STUDENTS -> {
                // 匹配整个表,直接使用传入的 selection 和 selectionArgs
                rowDeleted = db.delete(StudentDbHelper.TABLE_STUDENTS, selection, selectionArgs)
            }
            STUDENT_ID -> {
                // 匹配单条记录
                // 从 URI 中解析出 ID
                val id = ContentUris.parseId(uri)
                // 构建新的 selection,将 ID 条件与原有的 selection 合并
                val newSelection = if (!TextUtils.isEmpty(selection)) {
                    "$selection AND ${StudentDbHelper.COLUMN_ID} = ?"
                } else {
                    "${StudentDbHelper.COLUMN_ID} = ?"
                }
                // 构建新的 selectionArgs,将 ID 添加到参数数组末尾
                val newSelectionArgs: Array<String?> = if (selectionArgs.isNullOrEmpty()) {
                    arrayOf(id.toString())
                } else {
                    selectionArgs.plus(arrayOf(id.toString()))
                }
                // 执行删除
                rowDeleted = db.delete(StudentDbHelper.TABLE_STUDENTS, newSelection, newSelectionArgs)
            }
            else -> {
                // 如果 URI 不匹配任何已知模式,则抛出异常
                throw IllegalArgumentException("Unknown URI: ${uri}")
            }
        }
        // 如果有行被删除,通知所有监听器数据发生了变化
        if (rowDeleted > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        return rowDeleted
    }

    /**
     * 【核心方法】返回指定 URI 所代表的数据的 MIME 类型。
     * 这是 ContentProvider 标准协议的一部分,帮助客户端了解数据的格式。
     * @param uri 数据的 URI。
     * @return 对应的数据类型 (MIME type),如果无法识别则抛出异常。
     */
    override fun getType(uri: Uri): String? {
        return when (uriMatcher.match(uri)) {
            // 对于集合类型,MIME 类型格式为:vnd.android.cursor.dir/vnd.<authority>.<path>
            STUDENTS -> "vnd.android.cursor.dir/vnd.$AUTHORITY.$PATH_STUDENT"
            // 对于单条记录类型,MIME 类型格式为:vnd.android.cursor.item/vnd.<authority>.<path>
            STUDENT_ID -> "vnd.android.cursor.item/vnd.$AUTHORITY.$PATH_STUDENT"
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }

    /**
     * 【核心方法】实现插入数据的功能。
     * @param uri 要插入数据的目标 URI(通常是表的 URI)。
     * @param values 一个 ContentValues 对象,包含了要插入的列名和对应的值。
     * @return 指向新插入记录的 URI(通常是在基础 URI 后追加新记录的 ID)。
     */
    override fun insert(
        uri: Uri,
        values: ContentValues?
    ): Uri? {
        if (uriMatcher.match(uri) != STUDENTS) {
            throw IllegalArgumentException("Insertion is not supported for URI: $uri")
        }
        db = dbHelper.writableDatabase
        // 执行插入操作,返回新记录的行 ID
        val id = db.insert(StudentDbHelper.TABLE_STUDENTS, null, values)
        // 如果插入成功 (id > 0)
        if (id > 0) {
            // 构建并返回新记录的 URI
            val newUri = ContentUris.withAppendedId(CONTENT_URI, id)
            // 通知数据变化
            context?.contentResolver?.notifyChange(newUri, null)
            return newUri
        }
        // 如果插入失败,抛出异常
        throw SQLException("Failed to insert row into $uri")
    }
    /**
     * ContentProvider 创建时调用的初始化方法。
     * 通常在这里初始化数据库帮助类。
     * @return 如果初始化成功则返回 true,否则返回 false。
     */
    override fun onCreate(): Boolean {
        // 在 Context 不为 null 的情况下初始化 StudentDbHelper
        // !! 操作符表示我们确信 context 在这里不为 null,这是一个常见的实践。
        dbHelper = StudentDbHelper(context!!)
        return true
    }
    /**
     * 【核心方法】实现查询数据的功能。
     * @param uri 要查询的 URI。
     * @param projection 要返回的列名数组 (null 表示返回所有列)。
     * @param selection SQL 的 WHERE 子句。
     * @param selectionArgs 用于替换 selection 中 "?" 的参数数组。
     * @param sortOrder SQL 的 ORDER BY 子句。
     * @return 一个 Cursor 对象,包含了查询结果。
     */
    @SuppressLint("Recycle") // 抑制关于 Cursor 未关闭的警告,因为 Cursor 由调用方负责关闭
    override fun query(
        uri: Uri,
        projection: Array<out String?>?,
        selection: String?,
        selectionArgs: Array<out String?>?,
        sortOrder: String?
    ): Cursor? {
        // 获取可读数据库
        db = dbHelper.readableDatabase
        val cursor: Cursor?
        when (uriMatcher.match(uri)) {
            STUDENTS -> {
                // 查询整个学生表
                cursor = db.query(
                    StudentDbHelper.TABLE_STUDENTS,
                    projection,
                    selection,
                    selectionArgs,
                    null, // groupBy
                    null, // having
                    // 如果未指定排序方式,则默认按姓名升序排列
                    sortOrder ?: "${StudentDbHelper.COLUMN_NAME} ASC"
                )
            }
            STUDENT_ID -> {
                // 查询单条记录
                val id = ContentUris.parseId(uri)
                cursor = db.query(
                    StudentDbHelper.TABLE_STUDENTS,
                    projection,
                    // 构建 WHERE 子句,通过 ID 进行筛选
                    "${StudentDbHelper.COLUMN_ID}=?",
                    // 传入 ID 作为参数,防止 SQL 注入
                    arrayOf(id.toString()),
                    null,
                    null,
                    sortOrder
                )
            }
            else -> {
                throw IllegalArgumentException("Unknown URI: $uri")
            }
        }
        // 设置 Cursor 的通知 URI。当这个 URI 对应的数据发生变化时,
        // 这个 Cursor 会收到通知,从而可以刷新数据。这是实现数据实时更新的关键。
        cursor.setNotificationUri(context?.contentResolver, uri)
        return cursor
    }

    /**
     * 【核心方法】实现更新数据的功能。
     * @param uri 要更新数据的 URI。
     * @param values 包含了要更新的列名和对应新值的 ContentValues 对象。
     * @param selection SQL 的 WHERE 子句。
     * @param selectionArgs 用于替换 selection 中 "?" 的参数数组。
     * @return 被更新的行数。
     */
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String?>?
    ): Int {
        val db = dbHelper.writableDatabase
        var rowsUpdated: Int = 0

        when (uriMatcher.match(uri)) {
            STUDENTS -> {
                // 更新整个表中符合条件的记录
                rowsUpdated = db.update(StudentDbHelper.TABLE_STUDENTS, values, selection, selectionArgs)
            }
            STUDENT_ID -> {
                // 更新单条记录
                val id = ContentUris.parseId(uri)
                // 合并 selection 和 ID 条件
                val newSelection = if (!TextUtils.isEmpty(selection)) {
                    "$selection AND ${StudentDbHelper.COLUMN_ID} = ?"
                } else {
                    "${StudentDbHelper.COLUMN_ID} = ?"
                }
                // 合并 selectionArgs 和 ID 参数
                val newSelectionArgs: Array<String?> = if (selectionArgs.isNullOrEmpty()) {
                    arrayOf(id.toString())
                } else {
                    selectionArgs + id.toString()
                }
                // 执行更新
                rowsUpdated = db.update(StudentDbHelper.TABLE_STUDENTS, values, newSelection, newSelectionArgs)
            }
            else -> {
                throw IllegalArgumentException("Unknown URI: $uri")
            }
        }
        // 如果有行被更新,通知数据变化
        if (rowsUpdated > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        return rowsUpdated
    }
}

AndroidManifest.xml 文件中注册这个 ContentProvider

<manifest ...>
    <application ...>
        <!-- 其他组件 -->
        <provider
            android:name=".MyCustomProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="true"> <!-- 允许其他应用访问 -->
        </provider>

    </application>
</manifest>
访问 ContentProvider (作为数据使用方)

这里在一个activity中使用

首先获取对应的ContentResolver
由于上面的数据的操作是同步的,为了不阻塞主线程,使用协程进行数据操作
使用URI访问ContentProvider,并调用对应的方法
// 插入操作
lifecycleScope.launch(Dispatchers.IO) {
	val newStudentUri: Uri? = contentResolver.insert(
	StudentProvider.CONTENT_URI,
	ContentValues().apply {
     	put(StudentDbHelper.COLUMN_NAME, "b3q")
     	put(StudentDbHelper.COLUMN_GRADE, 114514)
   	}
  )
  Log.d(TAG, "Insert new student URI:$newStudentUri")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值