将Android进行到底之内容提供者(ContentProvider)

news/2024/9/16 22:10:19/

在这里插入图片描述

文章目录

  • 前言
  • 一、ContentProvider是什么?
  • 二、使用示例
    • 1.为应用创建内容提供者
    • 2.使用内容提供者
      • 2.1 内容URI
      • 2.2 Uri参数解析
      • 2.2 使用内容URI操作数据
    • 3.ContentProvider妙用
    • 4 内容URI对应的MIME类型
    • 5.ContentProvider重点注意
    • 6 演示demo源码
  • 总结


前言

随着现在的应用越做越大,出现了多进程的架构,因为Android的应用能申请的内存是有限制的,所以很多APP为了能够最大程度的保证自己的应用能够逃脱系统的围杀,设计出多进程架构,让App可以多使用几份内存。但是由于进程间内存是不共享的。所以需要做进程间的通信(IPC)。而Android进程间的通信有很多种。比如socket,基于Binder的AIDL以及ContentProvider等,而本章内容要介绍的就是内容提供者(ContentProvider)


一、ContentProvider是什么?

内容提供者ContentProvider是Android的四大组件之一,是Android进程间通信的一种实现方式,底层也是基于Binder实现的。它主要用于在不同的应用程序之间实现数据共享的功能,而且它提供了一套完整的机制。允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。不同于文件存储和sharedPreferences存储中两种全局可读可写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会泄漏。目前,内容提供者已经成为官方推荐的进程间共享数据的标准方式。


二、使用示例

1.为应用创建内容提供者

创建一个类继承自Android官方提供的ContentProvider类,官方提供的ContentProvider类共有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。我们以经典例子雇员和雇主的例子介绍内容提供者的使用,假设我们要共享雇员的信息给另外一个程序。代码如下所示:

public class EmployeeProvider extends ContentProvider {@Overridepublic boolean onCreate() {//初始化ContentProvider的时候调用通常会在这里完成对数//据库的创建和升级操作,返回true表示内容提供者初始//化成功,false表示失败。注:只有当存在ContentProvider//尝试访问我们程序中的数据时,ContentProvider才会被初始化return false;}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[]selectionArgs, @Nullable String sortOrder) {//query 方法用于从ContentProvider中查询数据。使用Uri参数//确定查询的表,projection参数确定查询的列,selection和//selectionArgs用于约束查询哪些行,sortOrder用于对查询结果//排序,查询的结果存放在Cursor对象中返回return null;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {//根据传入的内容URI来返回相应的MIME类型return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {//向ContentProvider中添加一条数据,使用Uri参数来确定要添加的表,待添加的数据保存//在values参数中,添加完成后,返回一个用于表示这条新纪录的Urireturn null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {//从ContentProvider中删除数据,使用uri参数来确定删除哪一张表中的数据,selection//和selectionArgs参数用于约束删除哪些行,被删除的行数作为返回值返回return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values,@Nullable String selection, @Nullable String[] selectionArgs) {//更新ContentProvider中已有的数据,使用Uri参数确定更新哪一张表中的数据新数据//保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响//的行数会作为返回值返回return 0;}
}

上面的六个抽象方法中相信聪明的你一眼就能看出,这是一些增删改查的方法。但是我们几乎可以在每个方法中看到Uri这个参数,我们在使用内容提供者的时候会传入这个参数,接下来先让我们看下如何使用内容提供者。

2.使用内容提供者

我们使用ContentProvider一般都是用来查询数据的,下面就让我们重点看下内容提供者的query方法

Cursor cursor = query(@RequiresPermission.Read @NonNull Uri uri,@Nullable String[] projection, @Nullable String selection,@Nullable String[] selectionArgs, @Nullable String sortOrder,@Nullable CancellationSignal cancellationSignal);

参数解释
uri:指定查询某个程序下的某一张表
projection:指定查询的列名
selection:指定查询条件,相当于Sql语句中where后面的条件
selectionArgs:给selection中的占位符提供具体的值
sortOrder:指定查询的结果排序方式
cancellationSignal:取消正在进行操作的信号量

访问内容提供者共享的数据需要借助于Android提供的ContentProvider类,我们可以通过Context中的getContentResolver()方法获取该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD(增、删、改、查)操作。类似于Sqlite中对数据的操作。但又不是完全相同。ContentResolver中的增删改查方法是不接受表名参数的,而是使用一个Uri参数代替。这个参数被称为内容URI

2.1 内容URI

内容提供者URI给内容提供者中的数据建立了唯一标识符,它主要由两部分组成:authority和path。

authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用包名的方式来进行命名,比如应用的包名为:com.example.app,那对应的authority可以命名为:com.example.app.provider

path 则是用于对同一应用程序中不同的表做区分的,通常会添加到authority的后面。比如某个成T恤的数据库里面存在两张表:table1和table2,这时可以将path分别命名为/table1 和 /table2;然后把authority和path组合在一起就成了下面两行字符串:

com.example.app.provider/table1
com.example.app.provider/table2

不过目前这两个字符串还是无法辨认它就是内容URI,我们需要在字符串的头部加上协议声明:所以内容URI的标准形式就出来了:

content://com.example.app.provider/table1
content://com.example.app.provider/table2

得到内容URI后我们还需要将它解析程Uri对象才可以作为参数传入。解析的方法也很简单。如下所示

Uri uri = new Uri.parse("content://com.example.app.provider/table1");

只需要调用Uri的静态方法parse()就可以把内容URI字符串解析程URI对象,现在就可以通过这个URI去查询table1中的数据了。

2.2 Uri参数解析

经过上一节内容我们知道,一个标准的内容URI写法是:

content://com.example.app.provider/table1

这就表示调用方期望访问的是com.example.app这个应用的table1表中的数据。其实,我们还可以在内容URI的后面加上一个id,如下所示:

content://com.example.app.provider/table1/1

这就表示调用方期望访问的是com.example.app这个应用的table1表中的id为1的数据

所以,内容URI的格式主要有以上两种,以路径结尾就表示期望访问该表中的所有数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:
‘ * ’:表示匹配任意长度的任意字符
‘ # ’:表示匹配任意长度的任意数字

所以一个能匹配任意表的内容URI格式就可以写成

content://com.example.app.provider/table1/*

一个可以匹配任意一行数字内容的URI格式就可以写成

content://com.example.app.provider/table1/#

然后我们就可以借助UriMatcher这个类实现匹配内容URI的功能

2.2 使用内容URI操作数据

1.查询数据

  public void query(View view) {String [] PROJECTION = new String[]{Employee._ID,Employee.NAME,Employee.GENDER,Employee.AGE};Cursor cursor = getContentResolver().query(Employee.CONTENT_URI,PROJECTION,null,null,Employee.DEFAULT_SORT_ORDER);StringBuilder sb = new StringBuilder();if(cursor !=null && cursor.moveToFirst()){for (int i = 0; i < cursor.getCount(); i++) {cursor.moveToPosition(i);int id = cursor.getInt(0);String name = cursor.getString(1);String gender = cursor.getString(2);int age = cursor.getInt(3);sb.append("id").append(id).append("name: ").append(name).append(" ,gender: ").append(gender).append(" ,age: ").append(age).append("\n");}}if(cursor != null && !cursor.isClosed()){cursor.close();}//显示数据setText("");setText(sb.toString());}

2.插入数据

public void insert(View view) {Uri uri = Employee.CONTENT_URI;ContentValues values = new ContentValues();values.put(Employee.NAME,"walt");values.put(Employee.GENDER,"male");values.put(Employee.AGE,28);getContentResolver().insert(uri,values);Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();}

3.更新数据

    public void update(View view) {//更新ID为1的记录Uri uri = ContentUris.withAppendedId(Employee.CONTENT_URI,1);ContentValues values = new ContentValues();values.put(Employee.NAME,"walt-zhong");values.put(Employee.GENDER,"male");values.put(Employee.AGE,18);int update = getContentResolver().update(uri, values, null, null);}

4.删除数据

  public void delete(View view) {//删除ID为1的记录Uri uri = ContentUris.withAppendedId(Employee.CONTENT_URI,2);int delete = getContentResolver().delete(uri, null, null);setText("删除成功 result: " + delete);}

3.ContentProvider妙用

ContentProvider还有一个妙用,那就是可以跨进程调用方法。读者可能会很迷惑对于这个描述。我来假设一个场景,比如我们需要从A应用传递一个坐标给到B应用去做一些操作,比如在VR行业中有种手机模式场景,就是通过在VR眼镜中虚拟出一个手机桌面,让用户在3d的场景中也可以使用2d的手机应用。也称为2D应用3D化。这时候用户可能是通过游戏手柄去操作的,游戏手柄通过蓝牙和VR眼镜相连,当游戏手柄点击到一个专门检测用户点击的A进程后,A进程会把用户点击的屏幕坐标共享给VR眼镜,这时候就可以使用内容提供者的跨进程调用方法,并把坐标当成参数传入就可以了,在使用端解析参数就可以进行相关的操作了。
具体的做法是:
1.通过调用方法的方式,将参数携带的值传到ContentProvider的使用端

    public void callMethod(View view) {Bundle reqBundle = new Bundle();reqBundle.putInt("actionCode", 1129);reqBundle.putString("extraData","额外数据");Bundle func = getContentResolver().call(Employee.CONTENT_URI, "func", getPackageName(), reqBundle);// Log.d("zhongxj: ","res= " + func.toString());}
call(@NonNull Uri uri, @NonNull String method,@Nullable String arg, @Nullable Bundle extras)

Uri:指定要操作的某个程序下的某一张表
method:方法名称,用于区分是谁调用了call方法
arg:参数,没有的话传null
extra:封装在Bundle的参数,没有的话传null

2.在使用方的ContentProvider中重写call()方法,接收参数,做后续处理

  @Nullable@Overridepublic Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {Log.d("zhongxj", "method: " + method);if ("func".equals(method)) {int actionCode = extras.getInt("actionCode");String extraData = extras.getString("extraData");StringBuilder sb = new StringBuilder();sb.append("func arg: ").append(arg).append("actionCode: ").append(actionCode).append("extraData").append(extraData).append("methodName: ").append(method);callFuncRes = sb.toString();Log.d("zhongxj", "result: " + sb.toString());}return null;}

4 内容URI对应的MIME类型

我们在重写ContentProvider的方法中有一个方法叫getType(),这个方法可以根据传入的URI返回对应的MIME类型,接下来我们一起来了解下什么是MIME类型以及如何定义内容URI对应的MIME类型

MIME(Multipurpose Internet Mail Extensions): 多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。它是一个互联网标准,扩展了电子邮件标准,使其能够支持:非ASCII字符文本;非文本格式附件(二进制、声音、图像等);由多部分(multiple parts)组成的消息体;包含非ASCII字符的头信息(Header information)。

一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下的格式规定:
(1)必须以vnd开头
(2)如果内容URI以路径结尾,则后接" android.cursor.dir/ " ;如果内容URI以ID结尾,
则后接 " android.cursor.item/ "
(3)最后接vnd..
例如:content://com.example.app.provider/table1 的对应MIME类型可以写成

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

而content://com.example.app.provider/table1/1这个内容URI的MIME类型可以写成

vnd.android.cursor.item/vnd.com.example.app.provider.table1

5.ContentProvider重点注意

写完ContentProvider后需要在AndroidManifest.xml中注册,否则程序会闪退

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.walt.mycontentprovider"><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.MyContentProvider"tools:targetApi="31"><activity>.......</activity><providerandroid:authorities="com.walt.provider.Employee"android:name=".EmployeeProvider"android:exported="true"android:enabled="true"/></application></manifest>

另外,还需注意的是在调用方使用ContentProvider时,在高版本的Android中需要在Androidmanifest.xml中加入标签,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.walt.appdemo2"><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.MyContentProvider"><activity>...</activity></application><!--高版本中必须加这个标签才能访问到对应应用的数据--><queries><provider android:authorities="com.walt.provider.Employee"/></queries>
</manifest>

6 演示demo源码

为了方便读者了解ContentProvider的使用,我做了一个简单的小demo,供读者参考,demo中有两个应用,分别代表A,B进程,一个创建了内容提供者,另一个使用内容提供者。需要注意的是,当使用方正在使用内容提供者的时候,提供数据的一方,即创建内容提供者的进程应该在后台,不能杀掉,否则,使用方无法获取到数据。
在这里插入图片描述这里需要声明下,demo仅做演示使用,请勿用到项目中,读者需要了解透彻后自己再开发出更好的代码。demo未经过测试,请勿使用:
demo地址


总结

以上就是今天要讲的内容,本文介绍了ContentProvider的使用和一些基本概念。并提供了一个演示demo供读者熟悉ContentProvider,但是在项目中使用ContentProvider时,读者还需自己仔细理解每一个API的使用方法,防止项目出现严重的BUG,博客只能是某个知识入门了解使用的工具,不是绝对的权威,希望读者能够结合自己的思考去实现更酷更好玩的东西。内容提供者作为现在Android应用间共享数据的标准方式,也是官方推荐的方式,希望读者好好的去了解这部分知识,对项目的开发一定会非常的有用。


http://www.ppmy.cn/news/8728.html

相关文章

[ASIS 2022 last CTF] 2022最后一赛

这个比赛太难了&#xff0c;就作了4个题 Crypto Bedouin 题目非常短&#xff0c;就是先生成一个小素数&#xff0c;然后堆到一起l次再补个1&#xff0c;比如235就变成2352352351这样&#xff0c;一开始以为是2进制&#xff0c;一直没作出来&#xff0c;方式也没错。后来发现原…

Unsupported conversion from LONG to java.sql.Timestamp

使用mybatisplus查询实体时报错Unsupported conversion from LONG to java.sql.Timestamp 先说结论&#xff1a; mybatis建议实体类上带上无参构造,当然java类虽然默认提供无参构造&#xff0c;但是现在都会用Data注解简化开发&#xff0c;里面会有 有参构造 所以默认的无参构造…

python自动化编程--正则表达式

目录 一.创建正则表达式 1.re模块 2.匹配Regex对象 二.正则表达式匹配更多模式 1.用括号分组 2.用管道匹配多个分组 3.用问号表示可选 4.用星号匹配零次或多次 5.用加号表示匹配一次或多次 6.用花括号匹配特定次数 三.贪心和非贪心匹配 四.字符分类 五.自定义字符…

QML教程(七) JavaScript

目录 一、对属性值使用 JavaScript 表达式 二、在 QML 中添加 JavaScript 函数 三、使用 JavaScript 文件 四、属性绑定中的 JavaScript 五、信号处理程序中的 JavaScript 六、将信号连接到 JavaScript 函数 七、启动执行 JavaScript QML 提供的 JavaScript 主机环境可以…

系统管理员喜欢 systemd 的 5 个理由

导读systemd 的速度和易用性使其成为管理现代 Linux 系统的流行方式。 系统管理员知道&#xff0c;在一台运行着的现代计算机上会发生很多事情&#xff1a;应用程序在后台运行、预定事件等待在特定时间被触发、事件写入日志文件、发送状态报告。在以前&#xff0c;不同的进程可…

2022年终总结与展望

2022年终总结 自2019年3月13日入驻CSDN&#xff0c;已经三年零九个月了。截至2022年12月31日&#xff0c;CSDN博客已发原创博文112篇&#xff0c;粉丝3616个&#xff0c;访问量超过157万次。 2019年12月31日数据情况&#xff1a; 2020年12月31日数据情况&#xff1a; 2021年1…

JAVA练习8

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、题目1- 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 二、题目2- 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示&#xff1a;这里可以…

数据结构(一)

单链表 // head存储链表头&#xff0c;e[]存储节点的值&#xff0c;ne[]存储节点的next指针&#xff0c;idx表示当前用到了哪个节点 int head, e[N], ne[N], idx; // 初始化 void init() { head -1; idx 0; } // 在链表头插入一个数a void insert(int a) { e[idx] a, ne[i…

mysql事务一致性,原子性,持久性实现以及锁区别

Mysql事务一致性&#xff0c;原子性是如何实现的? 首先是通过锁和mvcc实现了执行过程中的一致性和原子性 其次是在灾备方面通过Redo log实观&#xff0c;Redo log会把事务在执行过程中对数据库所做的所有修改都记录下来&#xff0c;在之后系统崩溃重启后把事务所做的任何修改都…

Faster RCNN网络源码解读(Ⅸ) --- ROIAlign、TwoMLPHead、FastRCNNPredictor部分解析

目录 一、回顾以及本篇博客内容概述 二、代码解析 2.1 FasterRCNNBase类 2.1.1 forward正向传播 2.2 FasterRCNN类 2.2.1 roi_heads定义 2.3 TwoMLPHead类&#xff08;faster_rcnn_framework.py&#xff09; 2.4 FastRCNNPredictor类 2.5 RoIHeads类&#xff08;roi_…

四【Servlet基础】文件配置及环境搭建(重要)

文章目录4.1 Servlet概念4.2 Servlet作用4.3 Servlet开发步骤4.3.1 搭建开发环境4.3.2 创建项目4.3.3 部署Servlet4.3.4 配置Servlet4.3.5 测试运行4.1 Servlet概念 &#xff08;1&#xff09;Servlet&#xff1a;Server Applet的简称&#xff0c;是运行在Web服务器端的Java程…

2.0、Linux-基础了解

2.0、开机关机和基本目录介绍 开机登录&#xff1a; 开会机会启动许多程序&#xff1b;他们在Windows叫做 "服务" &#xff0c;在 Linux 中叫做 "守护进程"&#xff08;daemon&#xff09;&#xff1b; 开机成功后&#xff0c;他会显示一个文本登录…

【网络】网络发展,网络协议,网络传输流程,地址管理

目录 1.计算机网络背景 1.1网络发展 局域网和广域网 1.2 协议 2.网络协议初识 2.1协议分层 2.2OSI七层模型 2.3 TCP/IP 五层&#xff08;或四层&#xff09;模型 网络和操作系统之间的关系 2.4重谈协议 -- 计算机的视角&#xff0c;如何看待协议&#xff1f; 2.5 网…

【MySQL】MySQL存储过程与存储函数实战(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

第三十六讲:无线AP胖AP模式配置与管理

胖AP(Fat AP)配置一个开放式WLAN非常方便&#xff0c;需要完成的操作包括有线和无线两部分的配置。有线部分即ethernet接口的配置&#xff0c;保证AP能够接入Internet,无线部分的配置包括关联WLAN与VLAN&#xff0c;广播SSID,启用VAP&#xff0c;若无其他DHCP服务器的话&#x…

高并发系统设计 -- 服务限流算法

常见的限流算法 漏桶算法 漏桶法的关键点在于漏桶始终按照固定的速率运行&#xff0c;但是它并不能很好的处理有大量突发请求的场景&#xff0c;毕竟在某些场景下我们可能需要提高系统的处理效率&#xff0c;而不是一味的按照固定速率处理请求。 关于漏桶的实现&#xff0c;u…

计算机网络体系结构

目录常见的计算机网络体系结构计算机网络体系结构分层的必要性计算机网络体系结构分层思想举例计算机网络体系结构中的专用术语常见的计算机网络体系结构 TCP/IP体系结构相当于将OSI体系结构的物理层和数据链路层合并为网络接口层。并去掉了会话层和表示层。 由于TCP/IP在网络…

应用上K8S:Gradle打包

需求 对于spring boot项目我们一般使用Maven或Gradle进行编译打包&#xff0c;也可以借助docker plugin进行镜像打包并push到远程仓库。因此在经过《Docker随时随地玩转变量》一文&#xff0c;我们已经确定了Dockerfile&#xff0c;那么应用上K8S第二步&#xff1a;gradle打包…

D2. RGB Substring (hard version)(尺取)

Problem - 1196D2 - Codeforces 通用领域 医学 计算机 金融经济 你有一个包含n个字符的字符串s&#xff0c;每个字符是R&#xff0c; G或B。 你还得到一个整数k。你的任务是改变初始字符串s中的最小字符数&#xff0c;这样在改变之后&#xff0c;将会有一个长度为k的字符串…

gateway基本配置

目录 1、gateway简介 2、gateway核心概念 3、路由 4、断言 5、过滤器 5.1、过滤器介绍 5.2、内置局部过滤器与使用 5.3、内置全局过滤器 5.4、自定义全局过滤器 5.4.1、黑名单校验 5.4.2、模拟登录校验 6、一个简单的gateway配置实例 1、gateway简介 路由转发 执行…