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

news/2024/4/26 5:37:29/文章来源:https://blog.csdn.net/jjwwmlp456/article/details/128420601

文章目录

  • 前言
  • 定义一个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()
}

点击查看完整的示例代码


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_240160.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从根本上理解Synchronized的加锁过程

伸手摘星&#xff0c;即使一无所获&#xff0c;亦不致满手污泥。 请关注公众号&#xff1a;星河之码 作为一个Java开发&#xff0c;对于Synchronized这个关键字并不会陌生&#xff0c;无论是并发编程&#xff0c;还是与面试官对线&#xff0c;Synchronized可以说是必不可少。 …

计算机视觉与图形学-神经渲染专题-非刚体NeRF II

《TiNeuVox:Fast Dynamic Radiance Fields with Time-Aware Neural Voxel》链接&#xff1a;https://jaminfong.cn/tineuvox/摘要作者通过表示具有时间感知体素特征的场景提出了一个辐射场框架&#xff0c;并将其命名为 TiNeuVox。作者引入了一个微小的坐标变形网络来模拟粗略的…

Day 04 - Composition API_ref reactive 函数

1.ref函数 作用: 定义一个响应式的数据&#xff1b; 语法: const xxx ref(initValue) 创建一个包含响应式数据的引用对象&#xff08;reference对象&#xff0c;简称ref对象&#xff09;。 JS中操作数据&#xff1a; xxx.value 模板中读取数据: 不需要.value&#xff0c;直…

Vue CLI系列之生成打包报告

文章の目录一、通过命令行参数的形式生成报告二、通过可视化的UI面板直接查看报告写在最后打包时&#xff0c;为了直观地发现项目中存在的问题&#xff0c;可以在打包时生成报告。生成报告的方式有两种&#xff1a; 一、通过命令行参数的形式生成报告 "scripts": {…

【Axure高保真原型】移动端钱包原型模板

今天和大家分享移动端钱包的原型模板&#xff0c;里面包含了11大模块&#xff0c;各个模块都是高保真高交互的原型模板&#xff0c;大家可以在演示地址里体验哦 【原型预览及下载地址】 https://axhub.im/ax9/4c3757a85d201a4c/#c1 这个原型还可以在手机上演示哦&#xff0c…

【精准三点定位求解汇总】利用Python或JavaScript高德地图开放平台实现精准三点定位(经纬度坐标与平面坐标转换法求解、几何绘图法求解)

【精准三点定位求解汇总】利用Python或JavaScript高德地图开放平台实现精准三点定位&#xff08;经纬度坐标与平面坐标转换法求解、几何绘图法求解&#xff09; 众所周知&#xff0c;如果已知三个点的坐标&#xff0c;到一个未知点的距离&#xff0c;则可以利用以距离为半径画…

JavaScript的原型链

JavaScript的原型链 JavaScript的继承主要是通过原型链实现的&#xff0c;所以理解原型链是掌握JavaScript继承的关键一环。原型链的继承的基本思想是通过原型链继承多个引用类型的属性和方法。 理解原型链 关于原型链的定义与理解&#xff1a; 每个构造函数都有一个原型对…

Sentinel流控

Sentinel 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 1.sentinel特性 Sentinel 具有以下特征: 丰富的应用场景&#xff1a; Sentinel 承接了…

【折腾服务器 3】群晖学习版中安装 Active Backup for Business 及相关配置 =)

Catch UP 书接上回&#xff0c;在 ESXi 中安装了群晖系统&#xff0c;这个系统主要是用来给 Windows 物理机做备份的&#xff0c;因此在本片主要讲解如何配置 Active Backup for Business 软件。 Chapter 1 设置存储空间 上一篇博客中&#xff0c;安装群晖时分配了一个 32GB…

【高精度定位】关于GPS、RTK、PPK三种定位技术的探讨

高精度定位通常是指亚米级、厘米级以及毫米级的定位&#xff0c;从市场需求来看&#xff0c;定位的精度越高往往越好。“高精度、低成本”的定位方案无疑将是未来市场的趋势。 在物联网时代&#xff0c;大多数的应用或多或少都与位置服务相关联&#xff0c;尤其是对于移动物体而…

「 理财与风险控制|养老系列」你想象中的高端养老社区是什么样?

本文主要介绍为什么养老规划需要考虑养老社区的部分&#xff0c;当前市场上养老社区的各种现状&#xff0c;养老社区从各个角度分类&#xff0c;选择养老社区需要关注的要素以及保险保单能够提供的养老权益是怎样的 文章目录01 为什么要关注养老社区&#xff1f;02 为什么关注高…

【docker常用命令】

一、帮助启动类命令 &#xff08;1&#xff09;启动docker systemctl start docker&#xff08;2&#xff09;停止docker systemctl stop docker&#xff08;3&#xff09;重启docker systemctl restart docker&#xff08;4&#xff09;查看docker状态 systemctl status…

移动端测试必备技能: adb命令和抓包

移动端测试 是指对移动应用进行的测试&#xff0c;即实体的特性满足需求的程度&#xff0c;进行测试前需要搭建测试环境。 1 移动端自动化环境搭建 1.1 java安装 java JDK 安装jdk-8u181-windows-x64.exe 配置环境变量&#xff1a; JAVA_HOME&#xff1a;D:\developer to…

【c++】STL--vector

前言 想必大家已经对string有所了解了&#xff0c;string是专门用于字符串的。今天讲到的vector则是表示可变大小数组的序列容器。就像数组一样&#xff0c;vectoer也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组…

SpringBoot系列之自动装配原理详解

文章目录前言一、SpringBoot自动配置-Condition-11、观察spring自动创建bean过程2、创建自定义bean对象3、根据条件创建自定义bean二、 SpringBoot自动配置-Condition-2三、SpringBoot自动配置-切换内置web服务器1、查看继承关系图2、shiftdelete 排除Tomcat四、SpringBoot自动…

12.20工作学习记录 力扣 罗马文转数字

每日一题:罗马文转数字 定义两个指针 不断后移 每一次让前一个指针的值累加为sum 最后返回sum 力扣https://leetcode.cn/problems/roman-to-integer/ 最长公共前缀 主要是subString方法 力扣https://leetcode.cn/problems/longest-common-prefix/solutions/现在分词与形容…

圣诞的荒诞小故事并记录互联网协议-五层模型

今天敲代码敲着敲着灵光乍现&#xff0c;突然一个荒诞的故事&#x1f4a1;映入脑海。 1.未来和过去&#xff1a; 人高度发达&#xff08;以下称之为渡&#xff09; 渡可以打开时空穿越过去&#xff08;以下称之为旧迹&#xff09;&#xff0c;并且可以进随心所欲的来去自如&a…

基于yolov5s实践国际象棋目标检测模型开发

在我前面的一篇文章中讲解实现了基于改进的yolov5s-spd模型实现了五子棋目标对象检测模型系统的设计开发&#xff0c;这里紧接前文&#xff0c;突发奇想&#xff0c;是否可以借鉴同样的思路实现象棋的检测模型开发呢&#xff1f;理论上面肯定是可以的&#xff0c;但是实际效果如…

详细介绍关于自定义类型:结构体、枚举、联合【c语言】

文章目录结构体结构体的声名特殊的声明结构成员的类型结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数结构体变量访问成员结构体传参结构体实现位段&#xff08;位段的填充&可移植性&#xff09;位段的内存分配位段的跨平台问题枚举枚举类型的定义枚举的…

微信小程序入门

目录 一&#xff0c;简介 二&#xff0c;小程序开发环境搭建 1.申请账号 2.安装开发工具 3.小程序工具使用 三&#xff0c;目录结构以及json配置 1.目录结果 2.json配置 3.JSON 语法 4.WXML 5.wxss 6.JS 逻辑交互 四&#xff0c;小程序宿主环境 1.程序与页面 2.组件…