文章目录
- 前言
- 定义一个ContentProvider的准备工作
- 定义authority
- 定义一个数据表的相关操作的 Uri 匹配规则
- room 数据库相关
- 示例
- manifest 定义
- MyContentProvider 实现
- 触发调用
前言
ContentProvider
这个东西,印象中,线上项目中,基本就没用过;前阵子,突然想,写个demo玩一下,后来发现room
不知道怎么结合到里面。再查了些资料,搞定了。
这个示例,断断续续地写了两三个周末了,上周完成了添加和条件查询操作;今天把模糊查询、删除、更新操作都完成了。
定义一个ContentProvider的准备工作
定义authority
authority 将用于 manifest 中注册 <provider>
、数据操作的Uri匹配规则,以及 ContentResolver
的增删改查方法 中的 uri
定义一个数据表的相关操作的 Uri 匹配规则
使用 UriMatcher
类;
matcher.addURI(String authority, String path, int code)
room 数据库相关
从 room
数据库中,可以获得 AppDatabase
实例;
从AppDatabase#getOpenHelper()
获取 SupportSQLiteOpenHelper
实例;
从SupportSQLiteOpenHelper#getReadableDatabase()
获取 只读的 SupportSQLiteDatabase
实例;
从SupportSQLiteOpenHelper#getWritableDatabase()
获取 只写的 SupportSQLiteDatabase
实例
示例
manifest 定义
<queries><!-- API 30后,调用方,需要在<queries>中定义 --><provider android:authorities="com.stone.cper" />
</queries>
<application><providerandroid:authorities="com.stone.cper"android:name=".ui.contentp.MyContentProvider"android:exported="true" />
</application>
MyContentProvider 实现
import android.content.ContentProvider
import android.content.ContentUris
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import com.stone.stoneviewskt.ui.room.appDatabase
import com.stone.stoneviewskt.util.logi/*** desc : ROOM + ContentProvider 示例* address 相关的增、删、改、查(模糊查)示例 都有了* 示例中关于 User表、User相关的Uri 等示例不全* author : stone* email : aa86799@163.com* time : 2022/12/13 14:33*/
class MyContentProvider : ContentProvider() {/** contentUri = "content://com.stone.cper/..."* 调用端:contentResolver.insert(CONTENT_URI, contentValues)*/private val matcher by lazy { UriMatcher(UriMatcher.NO_MATCH) }companion object {const val authority = "com.stone.cper" // 用于 contentUri, 用于 manifestconst val contentUriStr = "content://$authority"private const val FUNC_ADDRESS_ALL = 10private const val FUNC_ADDRESS_WHICH = 11private const val FUNC_ADDRESS_ADD = 12private const val FUNC_ADDRESS_DEL = 13private const val FUNC_ADDRESS_UPDATE = 14private const val FUNC_USER_DATA_ALL = 20private const val FUNC_USER_DATA_WHICH = 21private const val FUNC_USER_DATA_ADD = 22private const val FUNC_USER_DATA_DEL = 23private const val FUNC_USER_DATA_UPDATE = 24private const val FUNC_USER_DATA = 25private const val TABLE_ADDRESS = "address_data"private const val TABLE_USER = "UserData"}override fun onCreate(): Boolean {logi("MyContentProvider onCreate") // 初始化在 application的 attachBaseContext()和 onCreate()之间// matcher.addURI 建立对应关系matcher.addURI(authority, "address/all", FUNC_ADDRESS_ALL)matcher.addURI(authority, "address/item/add", FUNC_ADDRESS_ADD)matcher.addURI(authority, "address/item/del", FUNC_ADDRESS_DEL)matcher.addURI(authority, "address/item/update", FUNC_ADDRESS_UPDATE)matcher.addURI(authority, "address/item/#", FUNC_ADDRESS_WHICH) // 这里的#代表任意数字,本示例仅在查询使用,查询第几行数据matcher.addURI(authority, "address/*", FUNC_ADDRESS_ADD) // * 则代表匹配任意长度的任意字符,一般没啥实际意义matcher.addURI(authority, "user/all", FUNC_USER_DATA_ALL)matcher.addURI(authority, "user/item/#", FUNC_USER_DATA_WHICH) // 这里的#代表任意数字matcher.addURI(authority, "user/item/add", FUNC_USER_DATA_ADD)matcher.addURI(authority, "user/item/del", FUNC_USER_DATA_DEL)matcher.addURI(authority, "user/item/update", FUNC_USER_DATA_UPDATE)matcher.addURI(authority, "user/*", FUNC_USER_DATA) // * 则代表匹配任意长度的任意字符,一般没啥实际意义return true}/** 对于单个记录,返回的MIME类型应该以 vnd.android.cursor.item/ 为首的字符串* 对于多个记录,返回 vnd.android.cursor.dir/ 为首的字符串** return MIME类型字符串*/override fun getType(uri: Uri): String? {val name = when (getTableName(uri)) {TABLE_ADDRESS -> "address"TABLE_USER -> "user"else -> null}return if (uri.path?.contains("/all") == true) {"vnd.android.cursor.dir/$name"} else if (uri.path?.contains("/item") == true) {"vnd.android.cursor.item/$name"} else null}override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {val flag = when (matcher.match(uri)) {FUNC_ADDRESS_ALL, FUNC_ADDRESS_WHICH -> trueFUNC_USER_DATA_ALL, FUNC_USER_DATA_WHICH -> trueelse -> false}if (!flag) return null // 不符合uri规则就退出;否则 若是一个没有预定义的 uri,后续操作会引发错误val table = getTableName(uri)return appDatabase.openHelper.readableDatabase.query(SupportSQLiteQueryBuilder.builder(table).selection(selection, selectionArgs).columns(projection).orderBy(sortOrder).create())
// ContentUris.parseId(uri) // 可以获取到 uri 路径 最后 的数字,如 content://.../../9 获取到数字 9}override fun insert(uri: Uri, values: ContentValues?): Uri? { // values 中的 key 为表的 列名val flag = when (matcher.match(uri)) {FUNC_ADDRESS_ADD -> trueFUNC_USER_DATA_ADD -> trueelse -> false}if (!flag) return null // 不符合uri规则就退出;否则 若是一个没有预定义的 uri,后续操作会引发错误val rowId = appDatabase.openHelper.writableDatabase.insert(getTableName(uri), SQLiteDatabase.CONFLICT_REPLACE, values)return ContentUris.withAppendedId(uri, rowId)}override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {val flag = when (matcher.match(uri)) {FUNC_ADDRESS_DEL -> trueFUNC_USER_DATA_DEL -> trueelse -> false}if (!flag) return 0 // 不符合uri规则就退出;否则 若是一个没有预定义的 uri,后续操作会引发错误return appDatabase.openHelper.writableDatabase.delete(getTableName(uri), selection, selectionArgs)}override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {val flag = when (matcher.match(uri)) {FUNC_ADDRESS_UPDATE -> trueFUNC_USER_DATA_UPDATE -> trueelse -> false}if (!flag) return 0 // 不符合uri规则就退出;否则 若是一个没有预定义的 uri,后续操作会引发错误return appDatabase.openHelper.writableDatabase.update(getTableName(uri), SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs)}// 根据 uri,匹配出对应的数据表private fun getTableName(uri: Uri): String? {return when (matcher.match(uri)) {FUNC_ADDRESS_ALL, FUNC_ADDRESS_ADD, FUNC_ADDRESS_DEL, FUNC_ADDRESS_WHICH, FUNC_ADDRESS_UPDATE -> TABLE_ADDRESSFUNC_USER_DATA_WHICH, FUNC_USER_DATA -> TABLE_USERelse -> null}}
}
insert() 返回值是一个Uri对象。文档描述是,表示一个新插入项的 Uri。
ContentUris.withAppendedId(uri, rowId)
是在uri的尾部附加 “/rowId” 。而 uri 明明表示的是.../add
动作。后来尝试了ContentUris.withAppendedId("$contentUriStr/address/item".toUri(), rowId)
,实际就是形如content://com.stone.cper/address/item/2011193145
,发现也没问题。最后尝试了直接返回null,insert()和其它操作也是正常的。 … 这… 说明,sdk实现中,并没有强制什么规则?!
触发调用
使用ContentResolver
的增、删、改、查方法,需要传一个uri,以符合 MyContentProvider
中定义的规则
关于 address 相关的 uri 操作,在fragment中的实现:
private fun addressAll() {mAdapter.removeAll()val projection = arrayOf("*")// 如果为 null,在query 中,会查出所有列
// val projection = null // 如果为 null,在query 中,会查出所有列val selection = nullval selectionArgs = nullval order = "id asc"addressQuery("${MyContentProvider.contentUriStr}/address/all".toUri(), projection, selection, selectionArgs, order)
}private fun addressWhere() {mAdapter.removeAll()val projection = arrayOf("add_name", "phone", "address", "id")// 如果为 null,在query 中,会查出所有列val selection = "add_name=?" // 相当于 sql where 子句,不含 where 本身val selectionArgs = arrayOf("stone")val order = "id desc"addressQuery("${MyContentProvider.contentUriStr}/address/all".toUri(), projection, selection, selectionArgs, order)
}// 模糊查询 like 和 精确查询 = ,相结合
private fun addressFuzzyAll() {mAdapter.removeAll()val projection = arrayOf("add_name", "phone", "address", "id")// 如果为 null,在query 中,会查出所有列val selection = "add_name like '%stone%' and phone=?" // 相当于 sql where 子句,不含 where 本身。like 后面的值直接写在这,不能写到 selectionArgs 数组中。val selectionArgs = arrayOf("phone-557")val order = "id desc"addressQuery("${MyContentProvider.contentUriStr}/address/all".toUri(), projection, selection, selectionArgs, order)
}// 查询一条记录
private fun addressItem() {mAdapter.removeAll()val row = 2val projection = arrayOf("*")val selection = "rowid=?" // 相当于 sql where 子句,不含 where 本身。like 后面的值直接写在这,不能写到 selectionArgs 数组中。val selectionArgs = arrayOf(row.toString())val order = "id desc"addressQuery("${MyContentProvider.contentUriStr}/address/item/$row".toUri(), projection, selection, selectionArgs, order)
}private fun addressQuery(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, order: String?) {val resolver = requireActivity().contentResolverresolver.query(uri, projection, selection, selectionArgs, order)?.use { cursor ->cursor.moveToFirst()repeat(cursor.count) { // 这里用repeat来代替foreach,是因为循环中的 it 指代对象 在后续用不到val name = cursor.getString(cursor.getColumnIndex("add_name"))val phone = cursor.getString(cursor.getColumnIndex("phone"))val address = cursor.getString(cursor.getColumnIndex("address"))val id = cursor.getString(cursor.getColumnIndex("id"))logi("$id $name $phone $address")mAdapter.addItem("$id $name $phone $address")cursor.moveToNext()}}
}private fun addressAdd() {val random = Random.nextInt(0, 1000)val values = ContentValues()
// values.put("add_name", "add_name-$random")values.put("add_name", "stone-$random")values.put("phone", "phone-$random")values.put("address", "address-$random")val resolver = requireActivity().contentResolverresolver.insert(Uri.parse("${MyContentProvider.contentUriStr}/address/item/add"), values)
}private fun addressDelete() {val where = "add_name=?"val selectionArgs = arrayOf("add_name-580")val resolver = requireActivity().contentResolverresolver.delete("${MyContentProvider.contentUriStr}/address/item/del".toUri(), where, selectionArgs)addressAll()
}private fun addressUpdate() {val where = "add_name=?"val selectionArgs = arrayOf("add_name-882")val values = ContentValues()values.put("phone", "phone-1998")val resolver = requireActivity().contentResolverresolver.update("${MyContentProvider.contentUriStr}/address/item/update".toUri(), values, where, selectionArgs)addressAll()
}
点击查看完整的示例代码