ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用ContentProvider是Android实现跨程序共享数据的标准方式。
ContentProvider的用法一般有两种:
- 一种是使用现有的ContentProvider读取和操作相应程序中的数据;
- 另一种是创建自己的ContentProvider,给程序的数据提供外部访问接口。
1.0ContentResolver的基本用法
通过Context中的getContentResolver()方法获取ContentResolver类的实例。ContentResolver中提供了一系列的方法用于对数据进行增删改查操作,其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。
ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。
内容URI给ContentProvider中的数据建立了唯一标识符,它主要由两部分组成: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标准的格式如下:
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:
1.1查询数据
query()方法定义如下所示:
查询完成后返回的是一个Cursor对象,通过移动游标的位置遍历Cursor的所有行,然后取出每一行中相应列的数据,代码如下所示:
1.2添加数据
向table1表中添加一条数据,代码如下所示:
将待添加的数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将Uri和ContentValues作为参数传入即可。
1.3更新数据
更新这条新添加的数据,把column1的值清空,可以借助ContentResolver的update()方法实现,代码如下所示:
使用了selection和selectionArgs参数来对想要更新的数据进行约束,以防止所有的行都会受影响。
1.4删除数据
调用ContentResolver的delete()方法将这条数据删除掉,代码如下所示:
2.0使用现有的ContentProvider查询系统联系人
- ContactsContract.CommonDataKinds.Phone类提供了一个CONTENT_URI常量,而这个常量就是使用Uri.parse()方法解析出来的结果。
- 联系人姓名这一列对应的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
联系人手机号这一列对应的常量是ContactsContract.CommonDataKinds.Phone.NUMBER - 最后千万不要忘记将Cursor对象关闭。
修改AndroidManifest.xml中的代码,加入了android.permission.READ_CONTACTS权限,如下所示:
READ_CONTACTS权限属于危险权限,需要调用运行时权限的处理逻辑,具体使用方法,请看Android笔记——运行时权限
3.0创建自己的ContentProvider
3.1创建ContentProvider的步骤
3.1.1步骤一
要实现跨程序共享数据的功能,可以通过新建一个类去继承ContentProvider的方式来实现。ContentProvider类中有6个抽象方法,在使用子类继承它的时候,需要将这6个方法全部重写。代码示例如下:
(1) onCreate():初始化ContentProvider的时候调用。
通常会在这里完成对数据库的创建和升级等操作,返回true表示ContentProvider初始化成功,返回false则表示失败。
(2) query():从ContentProvider中查询数据。
uri参数用于确定查询哪张表
projection参数用于确定查询哪些列
selection和selectionArgs参数用于约束查询哪些行
sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回
(3) insert():向ContentProvider中添加一条数据。
uri参数用于确定要添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新记录的URI。
(4) update():更新ContentProvider中已有的数据。
uri参数用于确定更新哪一张表中的数据,新数据保存在values参数中
selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回。
(5) delete():从ContentProvider中删除数据。
uri参数用于确定删除哪一张表中的数据
selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
(6) getType():根据传入的内容URI返回相应的MIME类型。
3.1.2步骤二
一个标准的内容URI写法是:
内容URI的格式主要就只有以上两种:
- 以路径结尾表示期望访问该表中所有的数据,
-
以id结尾表示期望访问该表中拥有相应id的数据。
可以使用通配符分别匹配这两种格式的内容URI,规则如下:
● *表示匹配任意长度的任意字符。
● #表示匹配任意长度的数字。
借助UriMatcher这个类可以实现匹配内容URI的功能。
- UriMatcher中提供了一个addURI()方法,可以分别把authority、path和一个自定义代码传进去。
- 当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。
修改MyProvider中的代码,如下所示:
table1Dir表示访问table1表中的所有数据
table1Item表示访问table1表中的单条数据
table2Dir表示访问table2表中的所有数据
table2Item表示访问table2表中的单条数据
在MyProvider类实例化的时候创建UriMatcher的实例,调用addURI()方法,将期望匹配的内容URI格式传递进去。当query()方法被调用的时候,就会通过UriMatcher的match()方法对传入的Uri对象进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,则会返回相应的自定义代码,然后就可以判断出调用方期望访问的到底是什么数据了。
insert()、update()、delete()这几个方法的实现是差不多的,它们都会携带uri这个参数,然后同样利用UriMatcher的match()方法判断出调用方期望访问的是哪张表,再对该表中的数据进行相应的操作就可以了。
3.1.3步骤三
getType()方法是所有的ContentProvider都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。
一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定。
● 必须以vnd开头
● 如果内容URI以路径结尾,则后接android.cursor.dir/
如果内容URI以id结尾,则后接android.cursor.item/
● 最后接上vnd.<authority>.<path>
对于content://com.example.app.provider/table1
这个内容URI,它所对应的MIME类型就可以写成:
对于
content://com.example.app.provider/table1/1
这个内容URI,它所对应的MIME类型就可以写成:完善MyProvider中的getType()方法逻辑,代码如下所示:
一个完整的ContentProvider就创建完成了,现在任何一个应用程序都可以使用ContentResolver访问我们程序中的数据。
那么,如何才能保证隐私数据不会泄漏出去呢?
因为所有的增删改查操作都一定要匹配到相应的内容URI格式才能进行,而我们当然不可能向UriMatcher中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问,安全问题也就不存在了。
3.2实现自定义跨程序数据共享
3.2.1数据提供方
代码如下:
ContentProvider一定要在AndroidManifest.xml文件中注册才可以使用。
<application>标签内出现了一个新的标签<provider>,使用它来对DatabaseProvider进行注册。
- android:name属性指定了DatabaseProvider的类名
- android:authorities属性指定了DatabaseProvider的authority
- android:enabled属性表示是否启用这个ContentProvider
- android:exported属性表示是否允许外部程序访问我们的ContentProviderl