Android学习-跨程序共享数据-内容提供器

内容提供器简介

内容提供其(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供其是Android实现跨程序数据共享的标准方式。
不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险。

Android权限

Android将所有权限分成两类,普通权限和危险权限。普通权限指的是哪些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们授权,不需要用户手动操作,比如在广播测试中申请的权限。危险权限则表示那些可能会触及用户隐私,或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
下表为Android中所有的危险权限,一共是9组24个权限。
在这里插入图片描述

在程序运行时申请权限

首先新建一个RuntimePermissionTest项目,以CALL_PHONE权限为例。
这个权限是编写拨打电话功能的时候需要生命的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限。
在布局文件中定义一个按钮
在这里插入图片描述
逻辑是点击按钮时触发拨打电话的逻辑,修改活动中的代码。在这里插入图片描述
在这里定义了一个隐式Intent,Intent的action指定为Inten.ACTION_CALL,这是一个系统内置的打电话的动作,然后在data部分指定了写一时tel,号码是10086.Intent.ACTION_DIAL表示打开拨号界面,这个是不需要声明权限的。为了防止程序崩溃,我们将所有操作都放在了异常捕获代码块当中。

接下来修改AndroidManifest.xml文件,在其中声明如下权限。
在这里插入图片描述
由于是危险权限,所以我们需要进行运行时权限处理。
修改活动中的代码
在这里插入图片描述
第一步是判断用户是否已经给我们授权了,借助的是ContextCompat.checkSelfPermission方法。checkSelfPermisssion方法接收两个参数,第一个参数是Context,第二个参数是具体的权限名,比如打电话的权限名就是Manifest.permission.CALL_PHONE,然后我们使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较,相等就说明用户已经授权不等就表示用户没有授权。

如果已经授权,直接去执行拨打电话的逻辑操作就可以了,这里我们把拨打电话的逻辑封装到了call方法中。如果没有授权的话,则需要调用ActivityCompat.requestPermission方法来向用户申请授权,requestPermission方法接收三个参数,第一个参数是Activity的实例,第二个参数是一个String数组,我们把要申请的权限名放在数组中即可,第三个参数是请求码,只要是唯一值就可以了,这里传入了1.

调用完了requestPermission方法后,系统会弹出一个权限申请的对话框,然后用户可以选择同意或者拒绝我们的权限申请,不论是那种结果,最终都会回调到onRequestPermissionsResult方法中,而授权的结果则会封装在grantResults参数中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就会调用call方法来拨打电话,如果用户决绝的话我们只能放弃操作,并弹出一条失败提示。

访问其他程序中的数据

内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。Android系统自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就使得第三方应用程序可以充分的利用这部分数据来实现更好的功能。
ContentResolver的基本用法
对每一个应用程序来说,如果想要访问内容提供器种共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,insert,update,delete,query,名字和SQLiteDatabase一样。在方法参数上稍有一些区别。

不同于SQLiteDb,ContentResolver中的增删改查方法都是不接受表明参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。内容RUI给内容提供其中的数据建立了唯一的标识符,它主要由两部分组成:authority和path。authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序报名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。比如某个程序的数据库里存在两张表table1和table2,这时候就可以将path分别命名为/table1和/table2,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此内容URI最标准的写法格式如下。
在这里插入图片描述
如果使用表名的话,系统无法得知我们期望访问的是哪个应用程序里的表。

在得到内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入,解析方法如下。在这里插入图片描述
只需要调用Uri.parse方法,就可以将内容URI字符串解析成Uri对象了。现在我们就可以使用这个Uri对象来查询table1表中的数据了。代码如下在这里插入图片描述
下表对使用到的这部分参数进行详细解释
在这里插入图片描述
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。
读取的思路仍然是通过移动游标的位置来遍历Cursor的所有行,然后再去除每一行中相应列的数据,代码如下
在这里插入图片描述
插入
在这里插入图片描述
更新
在这里插入图片描述
删除
在这里插入图片描述

*下面例子用于读取联系人,并且在ListView中显示。
新建布局文件
在这里插入图片描述
修改活动中的代码在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
readcontacts方法里,这里书用了contentresolver中的query方法来查询系统的联系人数据。不过传入的Uri参数有些奇怪,没有调用Uri.parse方法去解析一个内容URI字符串。这是因为ContansContract.CommonDataKinds.Phone类已经帮我们做好封装,提供了一个CONTENT_URI常量,而这个常量就是使用Uri.parse方法解析出来的结果。接着我们对Cursor对象进行遍历,将联系人姓名和手机号这些数据逐个取出,联系人姓名这一列对应的常量是ContansContract.CommonDataKinds.Phone.DISPLAY_NAME,联系人手机号这一列对应的常量是ContansContract.CommonDataKinds.Phone.DISPLAY_NUMBER。两个数据都取出之后,将它们进行拼接,并且在中间加上换行符,然后将拼接后的数据添加到ListView的数据源里,并通知刷新一下ListView。最后将Cursor对象关闭。

adapter.notifydatasetchanged是更新listview的,改变了数据源就要刷新。
记得要声明权限
在这里插入图片描述

创建自己的内容提供器

刚刚我们访问其他应用程序的思路是:
获取到该应用程序的内容URI,然后借助ContenResolver进行CRUD操作就可以了。但那些提供外部访问接口的应用程序时如何实现这种功能的?如何保证安全性?

创建内容提供器的步骤
新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器。
ContentProvider类中有六个抽象方法,全部重写。新建MyProvider继承自ContentProvider。
在这里插入图片描述
在这里插入图片描述
回顾一下,标准的内容URI写法是:
在这里插入图片描述
这就说明我期望访问的是com.example.app这个应用table1表中的数据。
还可以再后面加一个id,比如加一个/1,就代表期望访问的是table1表中id为1的数据。

内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下
在这里插入图片描述
接着,再借助UriMatcher这个类就可以实现匹配URI的功能。
UriMatcher中提供了一个addURI的方法,这个方法接收三个参数,可以分别把authority、path和一个自定义代码传进去。这样,当调用UriMatcher的match方法时,就可以将一个Uri对象传入,返回值时某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。

修改MyProvider中的代码

在这里插入图片描述在这里插入图片描述
在这里新增了四个常量
TABLE1_DIR表示访问table1表中的所有数据
TABBLE1_ITEM表示访问table1表中的单条数据。
当query方法被调用的时候,就会通过UriMatcher的match方法传入的Uri对象进行匹配,如果发现UriMatcher中的某个内容URI格式成功匹配了该对象,则会返回相应的自定义代码,然后就可以判断出调用方期望访问的是什么数据。

get.Type方法:
它是所有内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,android对这3个部分做了如下格式规定。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
现在我们可以继续完善MyProvider中的呢日哦那个了,这次来实现getType方法中的逻辑,代码如下
在这里插入图片描述
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据。那么前面所提到的,如何才能保证隐私数据不会写路出去呢?其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了。因为所有CRUD操作都一定要匹配相应的内容URI格式才能进行,而我们当然不可能向UriMatcher中添加隐私数据的URI,所以这部分数据无法被外部程序访问到,安全问题也就不存在了。

实现跨程序数据共享
使用上一个DatabaseTest项目,首先将MyDatabaseHelper中使用Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast。然后创建一个内容提供器,在这里插入图片描述
会弹出如下
在这里插入图片描述
这里将内容提供器命名为DatabaseProvider,authority指定为com.example.databasetest.provider,Exported属性表示是否允许外部程序访问我们的内部提供器,Enabled属性表示是否启用这个内容提供器。
接着修改DatabaseProvider中的代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

query方法中,注意当访问单条数据的时候有一个细节,这里调用了Uri对象的getPathSegments方法,它会将内容URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0个位置就是路径,第1个位置存放的就是id了。得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。

insert方法中,insert方法要求返回一个能够表示这条新增数据的URI,所以我们还需要调用Uri.parse方法来将一个内容URI解析成Uri对象,当然这个内容URI时以新增数据的id结尾的。

update方法中,受影响的行数将作为返回值返回。

delete方法中,受影响的行数将作为返回值返回。

getType方法

注意:内容提供器需要在AndroidManifest.xml文件中注册才可以使用。如果使用AS的快捷方式创建的内容提供器,该步已被自动完成。

打开AndroidManifest.xml文件
在这里插入图片描述
在这里插入图片描述

说实话感觉,脑子里对这个结构有点不是很清晰

猜你喜欢

转载自blog.csdn.net/vbbbbbbbbbbb/article/details/113795330