ContentProvider详解

一、ContentProvider简介:
(一)、引入:
数据库在Android当中是私有的,不能将数据库设为WORLD_READABLE,每个数据库都只能创建它的包访问。这意味着只有创建这个数据库的应用程序才可访问它。也就是说不能跨越进程和包的边界,直接访问别的应用程序的数据库。那么如何在应用程序间交换数据呢? 如果需要在进程间传递数据,可以使用ContentProvider来实现。

(二)、ContentProvider的功能和意义:
为了在应用程序之间交换数据,Android提供了ContentProvider,ContentProvider是不同应用程序之间进行数据交换的标准API。当一个应用程序需要把自己的数据暴露给其他应用程序使用时,该应用程序可以通过提供ContentProvider来实现;而其他应用程序需要使用这些数据时,可以通过ContentResolver来操作ContentProvider暴露的数据。
一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作被暴露的内部数据,包括增加数据、删除数据、修改数据、查询数据等。
虽然大部分使用ContentProvider操作的数据都来自于数据库,但是也可以来自于文件、SharedPreferences、XML或网络等其他存储方式。

(三)、核心类:
1、ContentProvider:(A应用暴露数据)
一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据暴露出去
外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以修改程序的数据。
2、ContentResolver:(操作A应用所暴露的数据)
外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据
ContentResolver 可以理解成是HttpClient的作用。
3、 Uri:Uri是ContentResolver和ContentProvider进行数据交换的标识。
每个ContentProvider提供公共的URI来唯一标识其数据集。管理多个数据集的(多个表)的 ContentProvider 为每个数据集提供了单独的URI。
比如:content://com.example.app.provider/table1/…: (类比:http://www.sina.com.cn/sports/nba/….)
Uri 的标准前缀:以“content://”作为前缀,这个是标准的前缀,表示该数据由 ContentProvider 管理。
Uri 的authority部分:该部分是完整的类名。(使用小写形式保证他的唯一性)。
Uri 的path部分(资源部分、数据部分): 用于决定哪类数据被请求(一般写的就是表的名字)。
被请求的特定记录的id值。如果请求不仅限于某个单条数据,该部分及其前面的斜线应该删除。
为了将一个字符串转换成Uri,Android中提供了Uri的parse()静态方法来实现。

【备注】URI、URL、URN的区别:
首先,URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。
也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。
总结一下:URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,所以,是绝对的。

(四)、系统内置的预定义ContentProvider:
1、Contacts:获取、修改、保存联系人的信息
2、SMS:短信
3、CallLog :查看或更新通话记录
4、MediaStore:访问声音、视频、图片等多媒体文件
5、Browser:读取或修改浏览历史、网络搜索、书签
6、Setting:查看和获取蓝牙设置、铃声设置等设备首选项

二、使用ContentResolver使用(系统提供的ContentProvider全部都定义好了)

(一)、 使用ContentResolver 操作数据的步骤:
调用Context的getContentResolver()方法获得ContentResolver 对象;
调用使用ContentResolver 的insert()、delete()、update()、query()方法操作数据。
Uri insert(Uri uri, ContentValues values)
int delete(Uri uri, String where, String[] whereArgs)
Cursor query(Uri uri, String[] projection, String where, String[] whereArgs, String sortOrder)
int update(Uri uri, ContentValues values, String where, String[] whereArgs)
参数解释:
String where:表示带有占位符的where子句组成的字符串;
String[] whereArgs:表示替换where参数中占位符(?)后的数据组成的字符串数组;
String sortOrder:表示select语句中的order by子句组成的字符串;
String[] projection:表示select语句中需要查询返回的列。
ContentValues values:是由数据库中表字段和往该字段中放置的数据所组成的键值对对象。
【备注】以上四个方法的参数分别是2、3、4、5个。
注意:使用CursorAdapter的数据源cursor必须要有_id的字段

(二)、关于通话记录的数据操作
使用的Uri:content://call_log/calls
表中对应常用的字段的列名:
_id 主键
number 电话号码
name 联系人(如果没有这样的联系人信息,返回就是null)
type:通话类型 (1、来电记录;2、呼出记录;3、未接电话)
date 通话时间,接/未接电话的时间(年月日)(毫秒)
duration 通话时长(秒)
【备注】权限申请:android.permission.READ_CALL_LOG|android.permission.WRITE_CALL_LOG

(三)、关于短信的数据操作 (数据库路径: /data/data/com.android.providers.telephony/databases/mmssms.db)
1、常用使用的Uri:
1) content://sms/ (操作所有短信)
2) content://sms/inbox (收件箱)
3) content://sms/sent (已发送)
4) content://sms/draft (草稿)
5) content://sms/outbox (发件箱)
6) content://sms/failed (发送失败)
7) content://sms/queued (待发送列表)
2、表中对应常用的字段:
_id 主键
thread_id 序号,如果是同一个发件人,这里记录的thread_id一定以是一样的
address 发件人手机号码
person 联系人列表里的序号,null代表陌生人
date 日期(毫秒)
protocol 协议–(0.SMS_RPOTO, 1.MMS_PROTO)
read 是否阅读 (0未读, 1已读 )
status 接受状态 (-1接收,0完成, 64等待, 128失败)
type (0所有;1收件箱 ;2已发送;3草稿;4发件箱;5发送失败;6待发送列表)
body 短信内容
service_center 短信服务中心号码编号
3、sms对应的视图表threads常用字段意思 通过 (* from threads –)进行连接||扩展内容
_id 等于sms中的thread_id
message_count 短信的数量
snippet 最新一条短信的内容
date 最新一条短信对应的时间
【备注】权限申请

(四)、关于联系人的数据操作(数据库路径:/data/data/com.android.providers.contacts/databases/contacts2.db)

  从上图可知:总共有三张数据表(contact, row_contact , data)
  官方文档对三张表的解释:
1)ContactsContract.Contacts table
    Rows representing different people, based on aggregations of raw contact rows.
2)ContactsContract.RawContacts table
    Rows containing a summary of a person's data, specific to a user account and type.
3)ContactsContract.Data table
    Rows containing the details for raw contact, such as email addresses or phone numbers.

1、常用使用的Uri:
a) 联系人的Uri==> content://com.android.contacts/contacts 和 content://com.android.contacts/raw_contacts 和content://com.android.contacts/data
b )电话号码的Uri==> content://com.android.contacts/data/phones
c) EMAIL的URI==> content://com.android.contacts/data/emails

不过为了方便记忆,系统中提供了以下常量来替代以上的Uri字符串。(系统直接提供的Uri)

Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI; –> content://com.android.contacts/contacts
Uri PHONE_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; –> content://com.android.contacts/data/phones
Uri EMAIL_URI = ContactsContract.CommonDataKinds.Email.CONTENT_URI; –> content://com.android.contacts/data/emails

2、 data表中对应常用的字段:
    a) raw_contact_id :关联raw_contacts表的主键
    b) data1: 存放数据,包括(联系人、电话号码、Email)
    c) mimetype:配合data1使用(通过不同的类型区分是联系人,电话号码,Email)

【备注】权限申请

(五)、示例代码1——查看通讯录中的联系人姓名、id、电话、Email等信息:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    // 查询联系人
public static List<Map<String,Object>> queryContacts(ContentResolver resolver){
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Cursor cursorRawContacts;
Cursor cursorData;
//查找出raw_contacts的所有主键
cursorRawContacts = resolver.query(
Uri.parse(URL_RAW_CONTACTS),
new String[]{"_id"}, null, null, null);
// 默认在开始位置
while (cursorRawContacts.moveToNext()) {
//将_id加入到Map中
Map<String, Object> map = new HashMap<String, Object>();
int _id = cursorRawContacts.getInt(cursorRawContacts
.getColumnIndex("_id"));
map.put("_id", _id);
//根据raw_contacts找到指定的data
cursorData = resolver.query(
Uri.parse(URL_DATA),
new String[]{"data1","mimetype"},
"raw_contact_id = ?",
new String[]{_id +""}, null);
//从cursorData中获取联系人,电话号码,email
while (cursorData.moveToNext()) {
String data1 = cursorData.getString(cursorData.getColumnIndex("data1"));
String mimetype = cursorData.getString(cursorData.getColumnIndex("mimetype"));
if(mimetype.equals("vnd.android.cursor.item/name")){
map.put("name", data1);
}else if(mimetype.equals("vnd.android.cursor.item/phone_v2")){
// 可能有多个电话号码
if (!map.containsKey("phone")) {
map.put("phone", data1);
} else {
String phone = (String) map.get("phone");
// 定义拼接的规则
map.put("phone", phone + "&&" + data1);
}
}else if(mimetype.equals("vnd.android.cursor.item/email_v2")){
// 可能有多个Email
if (!map.containsKey("email")) {
map.put("email", data1);
} else {
String email = (String) map.get("email");
// 定义拼接的规则
map.put("email", email + "&&" + data1);
}
}
}
list.add(map);
}
return list;
}

(六)、示例代码2——添加联系人信息:

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
 //插入联系人信息
public static void insertContacts(ContentResolver resolver , Map<String,Object> data){
//先插入一条空的数据
ContentValues values = new ContentValues();
//获取插入返回的uri
Uri uriInsert = resolver.insert(Uri.parse(URL_RAW_CONTACTS), values);
//截取uri的最后位的数字,该数字表示raw_contact_id
long raw_contact_id = ContentUris.parseId(uriInsert);
//往data表中插入名字
values.clear();
values.put("raw_contact_id", raw_contact_id);
values.put("mimetype", "vnd.android.cursor.item/name");
values.put("data1", (String) data.get("name"));
values.put("data2", (String) data.get("name"));
resolver.insert(Uri.parse(URL_DATA), values);
// 往data表中插入电话号码
values.clear();
values.put("raw_contact_id", raw_contact_id);
values.put("mimetype", "vnd.android.cursor.item/phone_v2");
values.put("data1", (String) data.get("phone"));
values.put("data2", Phone.TYPE_MOBILE);
resolver.insert(Uri.parse(URL_DATA), values);
// 往data表中插入电话号码
values.clear();
values.put("raw_contact_id", raw_contact_id);
values.put("mimetype", "vnd.android.cursor.item/email_v2");
values.put("data1", (String) data.get("email"));
values.put("data2", Email.TYPE_HOME);
resolver.insert(Uri.parse(URL_DATA), values);
}

(七)、示例代码3——删除,修改联系人信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
       // 删除联系人(根据raw_contact的_id)
public static void deleteContacts(ContentResolver resolver, int _id) {
resolver.delete(Uri.parse(URL_RAW_CONTACTS), "_id = ?",
new String[] { _id + "" });
}
//更新联系人(根据row_contact的_id名字的替换)
public static void updateContacts(ContentResolver resolver, int _id ,String newName){
ContentValues values = new ContentValues();
// 更新raw_contacts表中的4列字段(名字和排序)
values.put("display_name", newName);
values.put("display_name_alt", newName);
values.put("sort_key", newName);
values.put("sort_key_alt", newName);
resolver.update(Uri.parse(URL_RAW_CONTACTS), values, "_id = ?", new String[] { _id + "" });
// 更新data表中的2列字段
values.clear();
values.put("data1", newName);
values.put("data2", newName);
resolver.update(Uri.parse(URL_DATA), values,
"raw_contact_id = ? and mimeType = ?", new String[] { _id + "", "vnd.android.cursor.item/name" });
}

(八)、Android为多媒体提供的ContentProvider的Uri如下:

MediaStore.Audio.Media.EXTERNAL_CONTENT_URI 存储在SD卡上的音频文件
MediaStore.Video.Media.EXTERNAL_CONTENT_URI 存储在 SD卡上的视频
MediaStore.Images.Media.EXTERNAL_CONTENT_URI 存储在 SD卡上的图片文件内容
MediaStore.Audio.Media.INTERNAL_CONTENT_URI 手机内部存储器上的音频文件
MediaStore.Video.Media.INTERNAL_CONTENT_URI 手机内部存储器上的视频
MediaStore.Images.Media.INTERNAL_CONTENT_URI 手机内部存储器上的图片
【数据库中主要字段的列名】

多媒体文件名称字段:Media.DISPLAY_NAME
详细描述字段:Media.DESCRIPTION
多媒体文件保存路径的字段:Media.DATA (/storage/mnt/icon.png)

五、当日作业:
1、利用ContentResolver+AutoCompleteTextView实现自动补全联系人姓名
2、利用ContentResolver制作管理SDCard上的多媒体文件管理器(可以显示图片,音频)
要求:左侧Fragment动态改变,右侧显示的内容动态变化(图片要求真实图片,音频视频不要求这是图片),文件名字显示必须要正确

1
2
3
4
5
6
7
8
9
10
11
12
// 手动更新媒体库?
private void updateGallery(String filename)//filename是我们的文件全名,包括后缀哦
{
MediaScannerConnection.scanFile(this,
new String[] { filename }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
}