最近系统学习了安卓开发中的四大组件之一的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/studentscontent://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 类
一个简单的键值对容器,用于在 insert 和 update 操作中传递数据。
它就像是一个通用的数据载体,避免了直接暴露数据库的 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")
}

2万+

被折叠的 条评论
为什么被折叠?



