几年开发两茫茫,且看我给你分析 ContentProvider
觉得文章太长的可以找我拿了完整的 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 代码的你一定对此方
法非常熟悉吧!等你看完后面 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 的后面。比如某个程序的数据库里存在两张表:table1
和 table2
,这时就可以将 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
参数当中,selection
和 selectionArgs
参数用于约束更新哪些行,受影响的行数将作为返回值返回。
5.delete()
方法:
从内容提供器中删除数据。使用 uri
参数来确定删除哪一张表中的数据,selection
和selectionArgs
参数用于约束删除哪些行,被删除的行数将作为返回值返回。
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;}
评论