写点什么

几年开发两茫茫,且看我给你分析 ContentProvider

用户头像
Android架构
关注
发布于: 1 小时前

觉得文章太长的可以找我拿了完整的 PDF 自行研究

参考:


(更多完整项目下载。未完待续。源码。图文知识后续上传 github。)可以点击关于我联系我获取完整 PDF(VX:mm14525201314)

1. 内容提供者是什么?

内容提供者(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供者是 Android 实现跨程序共享数据的标准方式。


不同于文件存储和 SharedPreferences 存储中的两种全局可读可写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会泄露的风险。

2.内容提供者的使用

我们一般用内容提供者都是用来查询数据的:

Cursor cursor = getContentResolver().query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)


  • uri,指定查询某一个程序下的某一张表

  • projection ,指定查询的列名

  • selection ,指定查询条件,相当于 sql 语句中 where 后面的条件

  • selectionArgs ,给 selection 中的占位符提供具体的值

  • orderBy ,指定查询结果排序方式

  • cancellationSignal ,取消正在进行操作的信号量


写过 SQLite 代码的你一定对此方


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


法非常熟悉吧!等你看完后面 ContentProvider 原理机制的时候,一定会恍然大悟吧!


想要访问内容提供者中共享的数据,就一定要借助 CotentResolver 类,可以通过 Context 中的 getContentResolver()方法获取该类的实例。ContentResolver 中提供了一系列的方法用于对数据进行 CRUD(增删改查)操作,其中 insert()方法用于添加数据,update()方法用于数据的更新,delete()方法用于数据的删除,query()方法用于数据的查询。这好像SQLite 数据库操作有木有?


不同于 SQLiteDatabase,ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个 Uri 的参数代替,这个参数被称作内容 URI。内容 URI 给内容提供者中的数据建立了唯一的标识符,它主要由两部分组成:authority 和 path。authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名为 com.example.app,那么该程序对应的 authority 就可以命名为 com.example.app.provider。path 则是用于对同一应用程序中不同的表做区分的,通常都会添加到 authority 的后面。比如某个程序的数据库里存在两张表:table1table2,这时就可以将 path 分别命名为/table1 /table2,然后把 authority 和 path 进行组合,内容的 URI 就变成了 com.example.app.provider/table1 com.example.app.provider/table2。不过目前还是很难辨认出这两个字符串就是两个内容 URI,我们还需要在字符串的头部加上协议声明。因此,内容 URI 最标准的格式写法如下:


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


在得到内容 URI 字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:


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


只需要调用 Uri 的静态方法 parse()就可以把内容 URI 字符串解析成 URI 对象。现在,我们可以通过这个 Uri 对象来查询 table1 表中的数据了。代码如下所示:


Cursor cursor = getContentResolver()query(uri,projection,selection,selectionArgs,sortOrder);


query()方法接收的参数跟 SQLiteDatabase 中的 query()方法接收的参数很像,但总体来说这个稍微简单一些,毕竟这是在访问其他程序中的数据,没必要构建复杂的查询语句。下标对内容提供者中的 query 的接收的参数进行了详细的解释:


查询完成仍然会返回一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来了。读取的思路仍然是对这个 Cursor 对象进行遍历,然后一条一条的取出数据即可,代码如下:


if(cursor != null){//注意这里一定要进行一次判空,因为有可能你要查询的表根本不存在 while(cursor.moveToNext()){String column1 = cursor.getString(cursor.getColumnIndex("column1"));int column2 = cursor.getInt(cursor.getColumnIndex("column2"));}}


查询都会了,那么剩下的增加,删除,修改自然也不在话下了,代码如下所示:


//增加数据 ContentValues values = new ContentValues();values.put("Column1","text");values.put("Column2","1");getContextResolver.insert(uri,values);


//删除数据 getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" });


//更新数据 ContentValues values = new ContentValues();values.put("Column1","改数据");getContextResolver.update(uri,values,"column1 = ?and column2 = ?",new String[]{"text","1"});

3. 如何创建属于自己应用的内容提供者?

前面已经提到过,如果要想实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以新建一个类去继承 ContentProvider 类的方式来创建一个自己的内容提供器.ContentProvider 类有 6 个抽象方法,我们在使用子类继承它的时候,需要将这 6 个方法全部重写。新建 MyProvider 继承字 ContentProvider 类,代码如下所示:


public class MyProvider extends ContentProvider {


@Overridepublic boolean onCreate() {return false;}


@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {return null;}//查询


@Overridepublic Uri insert(Uri uri, ContentValues values) {return null;}//添加


@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {return 0;}//更新


@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {return 0;}//删除


@Overridepublic String getType(Uri uri) {return null;}}


在这 6 个方法中,相信出来增删改查的方法你知道之外,剩下两个方法你可能不知道,下面就对这些方法进行一一介绍:

1.onCreate()方法:

初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作。返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在ContentResolver 尝试访问我们的程序中的数据时,内容提供器才会被初始化

2.query()方法:

从内容提供器中查询数据。使用 uri 参数来确定查询的哪张表,projection 参数用于确定查询的哪一列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。

3.insert()方法:

向内容提供器中添加一条数据。使用 uri 参数来确定要添加的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新纪录的 URI

4.update() 方法:

更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数据保存着 values 参数当中,selectionselectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

5.delete() 方法:

从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selectionselectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。

6.getType() 方法:

根据传入的内容 URI 来返回相应的 MIME 类型。可以看到,几乎每一个方法都会带有 Uri 这个参数,这个参数也正是调用ContentResolver 的增删改查方法时传递过来的。而现在,我们需要对传入的 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/*


而一个能够匹配 table 表中任意一行数据的内容 URI 格式就可以写成:


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


接着,我们再借助 UriMatcher 这个类就可以轻松地实现匹配内容 URI 的功能。UriMatcher 中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把 authority,path和一个自定义代码传进去,这个自定义代码其实就是一个 final 的 int 类型的具值。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改上述的 MyProvider 代码如下所示:


public class MyProvider extends ContentProvider {public static fianl int TABLE1_DIR = 0;public static fianl int TABLE1_ITEM = 1;public static fianl int TABLE2_DIR = 2;public static fianl int TABLE2_ITEM = 3;private static UriMatcher uriMatcher;static{uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);}...@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {switch(uriMatcher.match(uri)){case TABLE1_DIR://查询 table1 中的数据 break;case TABLE1_ITEM://查询 table1 中的单条数据 break;case TABLE2_DIR://查询 table2 中的数据 break;case TABLE2_ITEM://查询 table2 中的单条数据 break;}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
几年开发两茫茫,且看我给你分析ContentProvider