原理
每个应用程序的数据都是自己私有的,一般不会开放公有权限,但是当应用的使用规模较大,并衍生出其他以此为基础开发的应用,那么就需要为额外的应用开放一些权限,分享自己的一些数据供二次开发或使用。而 ContentProvider 的原理就是在自己的应用内部开放一个接口供其它应用调用,同时为了保证安全性,使用该接口需要进行安全验证。
其中接口的使用是基于 sqlite 的增删改查方法实现的。
在使用 ContentProvider 读写数据之前,先来看如何定义一个数据开放接口,之后我们使用起来就会更加清晰有条理。
实现 ContentProvider 开放接口
假设该应用本地已经建立一个数据库名为 data.db
,数据库中有一张名为 user
的表。
新建一个类
MyDbProvider
继承ContentProvider
,该类默认实现数据库操作的四个方法,也就是增、删、改、查。同时还有两个额外的方法:onCreate()
和getType(Uri uri)
,前者大家都很熟悉,是该对象被创建时执行的方法,后者我们暂且不提,后面会讲到。(暂且不去实现增删改查的逻辑)在清单文件中配置:
<provider name=".MyDbProvider" authority="com.alpha.db" android:exported="true">
其中authority
里写你自己定义的主机名,一般为自己 app 的包名,但也可以自由命名,如 google 的通话记录分享者主机名为call_log
;exported="true"
是 Android6.0 开始必须添加的属性,代表该组件可以被外部访问。新建一个静态
UriMatcher
类,用于设置接口的匹配规则。传入的参数代表匹配失败返回-1,是固定的用法。1
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
添加匹配规则(不必在
UriMatcher
类中添加):1
2
3
4static {
uriMatcher.addURI("com.alpha.db","user",User_ALL);
uriMatcher.addURI("com.alpha.db","user/#",User_Single);
}第一个参数为在清单文件中定义的主机名(同时也是 uri 的前缀),第二参数为匹配规则(
null
代表匹配所有),第三参数为自定义的匹配后返回值,必须为正整数。例如:
"content://com.alpha.db/user"
表示匹配user表中所有数据 设定返回值为1
"content://com.alpha.db/user/#"
表示匹配user表中的某一行数据 设定返回值为2实现 MyDbProvider 中的增删改查方法
具体的数据库操作逻辑这里不提;在进行数据库操纵之前,应该对得到的 Uri 进行匹配,是否与已定义的匹配类型吻合,若吻合则继续执行,否则应该抛出匹配失败的异常。
同时在前面提到的
getType(Uri uri)
方法里写入以下逻辑:(按需求在数据操纵方法中添加适当判断)1
2
3
4
5
6
7
8int code =uriMatcher.match(uri);
if (code==User_ALL){
return "vnd.android.cursor.dir/";//"vnd.android.cursor.dir/"表示该路径为一个目录
}else if (code==User_Single){
return "vnd.android.cursor.item/user";//"vnd.android.cursor.item/..."表示该路径为一个行或项
}else {
throw new IllegalArgumentException("匹配失败");
}
通过以上五步就定义好了一个 ContentProvider 开放接口,接下来就是在外部应用中使用这个接口了。
使用ContentProvider开放接口
获取 ContentProvider 的解析器 ContentResolver:
ContentResolver resolver = getContentResolver();
设置需要操作的数据库的路径:
Uri uri = Uri.parse("content://com.alpha.db/user");
以上为获取user表的全部数据,若只需要某一行,则为(12表示行数):
Uri uri = Uri.parse("content://com.alpha.db/user/12");
使用 resolver 的增删改查方法就可以对目标数据表进行操作
通过 resolver 的
query()
方法会返回一个 Cursor 对象,我理解为通过 sql 语句生成的游标在本地的引用,既然有了 Cursor 对象,那么 Cursor 中的数据你也是了然于胸了吧,毕竟 sql 语句可是你自己定义的。同样,resolver 的
insert()
方法会返回一个 uri,表示插入数据在该表中的位置;
update()
和delete()
会返回一个 int 数据,表示操作影响了几行。另外还可以通过 resolver 的
getType()
方法得到路径的类型,你可以分别对不同的路径类型执行不同的操作。
通过 ContentProvider 读写手机联系人
首先必须知道手机联系人的 ContentProvider 的主机名,表名,如果你看过 Android 上层应用的源码,你应该知道 Android 原生应用的联系人开放主机名为“com.android.contacts”,但是表就稍有点复杂,存储联系人涉及到三张表:
raw_contacts 保存联系人的 id,每一个联系人都有一个不同的id,名字叫 contact_id
data 保存联系人的数据,通过 raw_contact_id 来去识别这个数据属于哪个联系人
mimetype 保存数据的类型
查询联系人信息的步骤.
- 查询 raw_contacts 表, 得到每个联系人的 contact_id.
- 根据 contact_id 查询 data 表,把联系人的数据取出来.
- 根据联系人数据的 mimetype,获取数据代表什么含义.
原理明白了,下面直接上代码:(在这里我只查询 data 表中的 data1 列,和 mimetype 列)
1 | public class ContactsUtils { |
联系人实体类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class ContactsInfo {
private String name;
private String number;
private String email;
private String qq;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getQq() {
return qq;
}
public void setQq(String qq) {
this.qq = qq;
}
public String toString() {
return "[name="+name+",number="+number+",email="+email+",qq="+qq+"]";
}
}
之后需要查询联系人就只需要一行代码:
1 | List<ContactsInfo> contactsInfoList= ContactsUtils.getAllContactInfo(this); |
注意
如果是 Android6.0,则最好先获得运行时权限:
1 | //6.0需要申请运行时权限 |
写入联系人的步骤
因为涉及到3张表的关系,我们需要分别插入几张表,最后达到插入一个完整的联系人的目的。
- 在 raw_contacts 表里面添加联系人的 id。
- 根据这个添加的 id,在 data 表里面添加联系人的数据。
具体代码如下:(你可以把 info 对象替换为通过用户输入获取的信息)
1 | public void addContact(View view) { |
如图:
读写通话记录和短信
其实如果你已经明白和理解读写联系人,那么通话记录就是小菜一碟,毕竟通话记录是存在一张表中的。所以这里不再放具体的读写代码,仅放出对应的路径和表名。
- 通话记录
URI:"content://call_log/calls"
其中“call_log”为主机名,前文已经提过,calls为匹配规则,表示查询所有数据,关键数据列有”number”,”date”,”type”,分表表示号码、日期、呼叫类型; - 短信
URI:"content://sms/"
这里没有匹配规则,代表查询表中所有数据,包含发送的,接受的,草稿,发送失败等等。。关键数据列有”address”,”date”,”type”,”body”,分别表示对方号码,日期,短信类型(发送,接受,草稿,发送失败),以及短信内容。
以上读写联系人、短信、通话记录仅适用于 google 原生应用,第三方 rom 因为定制了出厂应用,内容提供者已经不再是原生的了。