Android ContentProvider中使用 room 并支持API 30(android 11)

Posted 匆忙拥挤repeat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android ContentProvider中使用 room 并支持API 30(android 11)相关的知识,希望对你有一定的参考价值。

文章目录

前言

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>
	<provider
        android: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, 用于 manifest
        const val contentUriStr = "content://$authority"

        private const val FUNC_ADDRESS_ALL = 10
        private const val FUNC_ADDRESS_WHICH = 11
        private const val FUNC_ADDRESS_ADD = 12
        private const val FUNC_ADDRESS_DEL = 13
        private const val FUNC_ADDRESS_UPDATE = 14

        private const val FUNC_USER_DATA_ALL = 20
        private const val FUNC_USER_DATA_WHICH = 21
        private const val FUNC_USER_DATA_ADD = 22
        private const val FUNC_USER_DATA_DEL = 23
        private const val FUNC_USER_DATA_UPDATE = 24
        private const val FUNC_USER_DATA = 25

        private 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 -> true
            FUNC_USER_DATA_ALL, FUNC_USER_DATA_WHICH -> true
            else -> 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 -> true
            FUNC_USER_DATA_ADD -> true
            else -> 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 -> true
            FUNC_USER_DATA_DEL -> true
            else -> 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 -> true
            FUNC_USER_DATA_UPDATE -> true
            else -> 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_ADDRESS
            FUNC_USER_DATA_WHICH, FUNC_USER_DATA -> TABLE_USER
            else -> 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 = null
    val selectionArgs = null
    val 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 = 2
    val 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().contentResolver
    resolver.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().contentResolver
    resolver.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().contentResolver
    resolver.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().contentResolver
    resolver.update("$MyContentProvider.contentUriStr/address/item/update".toUri(), values, where, selectionArgs)
    addressAll()

点击查看完整的示例代码


以上是关于Android ContentProvider中使用 room 并支持API 30(android 11)的主要内容,如果未能解决你的问题,请参考以下文章

Android中的ContentProvider源码解析

android-ContentProvider

Android开发实践 ContentProvider和ContentResolver

Android ContentProvider数据共享

Android ContentProvider完整案例

Android ContentProvider 使用