内容提供者(ContentProvider)
ContentProvider是Android四大组件中唯一不需要使用Intent来启动的组件
一般来说,应用的私有数据是不允许其他应用访问的,而ContentProvider的作用就是把应用的私有数据暴露给其他应用访问。使用ContentProvider可以自己定义访问规则,选择哪些私有数据暴露出去,哪些不暴露。
自定义ContentProvider(很少使用)
自定义一个ContentProvider将friend数据库中的私有数据暴露出去,步骤非常简单:
-
定义MyFriendProvider继承自ContentProvider,实现
onCreate()
、insert()
、delete()
、update()
、query()
、getType()
,代码如下/** * Created by wangshiyi on 2016/11/18. */ public class MyFriendProvider extends ContentProvider { private static final String AUTHORITY = "edu.neu.steve.provider.friend"; private static final int URI_CODE_CLASSMATES = 1; private static final int URI_CODE_TEAMMATES = 2; private static final int URI_CODE_CLASSMATES_QUERY_BY_ID = 3; private SQLiteDatabase db; // 创建URI匹配器,用于判断一条URI跟指定的多条URI中的哪条匹配从而实现操作不同的表 private static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 初始化块在构造器之前执行,在这里添加匹配规则 static { // arg0:指定ContentProvider的authorities // arg1:路径 // arg2:匹配码 sUriMatcher.addURI(AUTHORITY, "classmates", URI_CODE_CLASSMATES);// content://edu.neu.steve.provider.friend/classmates sUriMatcher.addURI(AUTHORITY, "teammates", URI_CODE_TEAMMATES); // content://edu.neu.steve.provider.friend/teammates sUriMatcher.addURI(AUTHORITY, "classmates/#", URI_CODE_CLASSMATES_QUERY_BY_ID);// content://edu.neu.steve.provider.friend/classmates/数字,多用于具体查询 } /** * 第一次创建ContentProvider时回调,在Application的onCreate()之前执行 * 一般在这里打开数据库 * * @return */ @Override public boolean onCreate() { MyOpenHelper oh = new MyOpenHelper(getContext(), "friend.db", null, 1); db = oh.getWritableDatabase(); // 打开friend数据库 return false; } /** * 把数据插入数据库,values为其他应用要插入的数据 * * @param uri * @param values * @return */ @Nullable @Override public Uri insert(Uri uri, ContentValues values) { switch (sUriMatcher.match(uri)) { case URI_CODE_CLASSMATES: db.insert("classmates", null, values); // 向classmates表中插入数据 break; case URI_CODE_TEAMMATES: db.insert("teammates", null, values); // 向teammates表中插入数据 break; default: throw new IllegalArgumentException("无匹配的URI"); } return uri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int i = 0; switch (sUriMatcher.match(uri)) { case URI_CODE_CLASSMATES: i = db.delete("classmates", selection, selectionArgs); // 从classmates表中删除数据 break; case URI_CODE_TEAMMATES: i = db.delete("teammates", selection, selectionArgs); // 从teammates表中删除数据 break; default: throw new IllegalArgumentException("无匹配的URI"); } return i; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int i = 0; switch (sUriMatcher.match(uri)) { case URI_CODE_CLASSMATES: i = db.update("classmates", values, selection, selectionArgs); // 更改classmates表中的数据 break; case URI_CODE_TEAMMATES: i = db.update("teammates", values, selection, selectionArgs); // 更改teammates表中的数据 break; default: throw new IllegalArgumentException("无匹配的URI"); } return i; } @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor cursor = null; switch (sUriMatcher.match(uri)) { case URI_CODE_CLASSMATES: cursor = db.query("classmates", projection, selection, selectionArgs, null, null, sortOrder, null);// 查询classmates表中的数据 break; case URI_CODE_TEAMMATES: cursor = db.query("teammates", projection, selection, selectionArgs, null, null, sortOrder, null); // 查询teammates表中的数据 break; case URI_CODE_CLASSMATES_QUERY_BY_ID: long id = ContentUris.parseId(uri); // 取出URI末尾携带的数字,作为查询的id cursor = db.query("classmates", projection, "_id = ?", new String[]{id + ""}, null, null, sortOrder, null); // 查询classmates表中指定id的数据 break; default: throw new IllegalArgumentException("无匹配的URI"); } return cursor; } /** * @param uri * @return 返回通过指定uri获取的数据的MIME类型 */ @Nullable @Override public String getType(Uri uri) { String type = null; switch (sUriMatcher.match(uri)) { case URI_CODE_CLASSMATES: type = "vnd.android.cursor.dir/classmates"; // 返回classmates的多条数据 break; case URI_CODE_TEAMMATES: type = "vnd.android.cursor.dir/teammates"; // 返回teammates的多条数据 break; case URI_CODE_CLASSMATES_QUERY_BY_ID: type = "vnd.android.cursor.item/classmates";// 返回classmates的单条数据 break; default: break; } return type; } }
上面代码在insert()
、delete()
、update()
、query()
方法中实现增删改查数据库的代码,供其他应用中的ContentResolver间接调用。
-
在清单文件中配置MyFriendProvider
<provider android:name=".MyFriendProvider" android:authorities="edu.neu.steve.provider.friend" android:exported="true"> </provider>
- authorities属性是ContentProvider的主机名(URI),功能类似于地址
- exported属性设置为true指明该ContentProvider允许被其它应用通过authorities获取
在其他应用中,获取ContentResolver通过URI访问自定义的MyFriendProvider,实现对friend数据库的操作
在实际开发中很少会用到自定义ContentProvider,因为作为一个普通的第三方应用,你所暴露的私有数据通常没有人感兴趣,除非你是给平台做接口的
系统提供的ContentProvider
实际上,Android系统为开发者提供了大量的ContentProvider,例如短信信息、联系人信息、系统的多媒体信息等,开发者自己开发的APP也可以通过ContentResolver来间接调用系统提供的ContentProvider所实现的insert()
、delete()
、update()
、query()
方法,这样开发者就可以获取到Android内部数据了。
获取短信
Android系统提供了Telephony应用程序(com.android.providers.telephony)来管理短信,而且还为短信管理提供了相应的ContentProvider,其他应用程序可以通过ContentResolver访问管理短信的ContentProvider,间接操作短信数据库mmssms.db,即可实现管理短信。
管理短信只涉及短信数据库的sms表,URI为content://sms
,通常我们只关注表中的4个字段:
- address:对方号码
- date:短信时间
- type:短信类型,1为收到,2为发送
- body:短信内容
下面的代码简单的实现了获取全部短信
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(Uri.parse("content://sms"), new String[]{"address", "date", "type", "body"}, null, null, null);
while (cursor.moveToNext()) {
String address = cursor.getString(0);
long date = cursor.getLong(1);
int type = cursor.getInt(2);
String body = cursor.getString(3);
System.out.println(address + ";" + date + ";" + type + ";" + body);
}
不要忘记在清单文件中注册读短信权限
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
为了防止第三方应用胡乱写入短信记录,Android 4.4之后只有默认的短信应用才有权限写入短信数据库
获取联系人
Android系统提供了Contacts应用程序(com.android.providers.contacts)来管理联系人,也为联系人管理提供了相应的ContentProvider,其他应用程序同样可以通过ContentResolver访问管理联系人的ContentProvider,间接操作联系人数据库contacts2.db,即可实现管理联系人。
下面列出了管理联系人主要涉及的联系人数据库中的三张表,以及表中需要关注的字段:
-
raw_contacts表
- contact_id:联系人ID
-
data表:保存联系人的详细信息,一条信息占一行,而不是一个联系人占一行
- data1:联系人信息的具体内容
- raw_contact_id:联系人ID,标识该条信息属于哪个联系人
- mimetype_id:标识该条信息属于什么MIME类型
mimetypes表:保存各个mimetype_id对应的MIME类型(姓名、号码、邮箱...)
下面的代码简单的实现了获取全部联系人到JavaBean并输出
ContentResolver resolver = getContentResolver();
// 先查询raw_contacts表获取所有联系人ID(/raw_contacts表示查询raw_contacts表)
Cursor cursorContactId = resolver.query(Uri.parse("content://com.android.contacts/raw_contacts"),
new String[]{"contact_id"},
null, null, null);
while (cursorContactId.moveToNext()) {
String contactId = cursorContactId.getString(0);
// 使用联系人ID作为where条件去查询属于该联系人的信息(/data实际上是多张表的复合查询)
Cursor cursorContactInfo = resolver.query(Uri.parse("content://com.android.contacts/data"),
new String[]{"data1", "mimetype"},
"raw_contact_id = ?",
new String[]{contactId},
null);
ContactBean contact = new ContactBean(); // 联系人JavaBean
while (cursorContactInfo.moveToNext()) {
String data1 = cursorContactInfo.getString(0);
String mimeType = cursorContactInfo.getString(1);
switch (mimeType) { // 根据不同的MIME类型把联系人信息保存到JavaBean
case "vnd.android.cursor.item/name":
contact.setName(data1);
break;
case "vnd.android.cursor.item/phone_v2":
contact.setPhone(data1);
break;
case "vnd.android.cursor.item/email_v2":
contact.setEmail(data1);
break;
default:
break;
}
System.out.println(contact);
}
}
同样不要忘记在清单文件中注册读联系*限
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
添加联系人
添加联系人,只需要向上面的三张表中插入新联系人的数据。
向raw_contacts表中插入新联系人的contact_id之前,需要先遍历这个表,以确定新联系人的contact_id是多少,注意这里不应该用已有的contact_id(如果用户删除了某个联系人,这个字段会置空)来确定新联系人的contact_id,只能依据主键_id来确定。新联系人的contact_id插入完成之后,我们就可以插入具体的联系人信息。
该过程的完整的代码如下:
ContentResolver resolver = getContentResolver();
// 先查询目前最新的联系人的主键_id,新联系人的ID在此基础上加1即可得到
Cursor cursorId = resolver.query(Uri.parse("content://com.android.contacts/raw_contacts"),
new String[]{"_id"},
null, null, null);
int _id = 0;
if (cursorId.moveToLast()) {
_id = cursorId.getInt(0);
}
int contactId = _id + 1;
// 向raw_contacts表中插入新联系人ID
ContentValues contentValues = new ContentValues();
contentValues.put("contact_id", contactId);
resolver.insert(Uri.parse("content://com.android.contacts/raw_contacts"), contentValues);
// 插入具体的联系人信息
// 插入名字
contentValues.clear();
contentValues.put("raw_contact_id", contactId);
contentValues.put("data1", et_name.getText().toString());
contentValues.put("mimetype", "vnd.android.cursor.item/name");
resolver.insert(Uri.parse("content://com.android.contacts/data"), contentValues);
// 插入号码
contentValues.clear();
contentValues.put("raw_contact_id", contactId);
contentValues.put("data1", et_phone.getText().toString());
contentValues.put("mimetype", "vnd.android.cursor.item/phone_v2");
resolver.insert(Uri.parse("content://com.android.contacts/data"), contentValues);
这里需要在清单文件中注册读联系*限和写联系*限
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
内容观察者(ContentObserver)
前面介绍的是当ContentProvider将数据暴露出来之后,ContentResolver会根据业务需要去主动查询ContentProvider所暴露的数据。有些时候,应用程序需要实时监听ContentProvider所暴露数据的变化,并做出响应,这就需要利用ContentObserver了。
ContentProvider在实现insert()
、delete()
、update()
时,会导致所暴露的数据发生改变,这时会调用如下代码通知所有注册在相应URI上的ContentObserver
getContext().getContentResolver().notifyChange(uri, null);
系统提供的ContentProvider已经在相应位置实现了这行代码,而我们自定义的ContentProvider就需要自己去实现
为了监听短信数据库的变化,定义一个SmsObserver继承ContentObserver,并重写onChange()
class SmsObserver extends ContentObserver {
public SmsObserver(Handler handler) {
super(handler);
}
/**
* 当所监听的数据发生改变时回调
*
* @param selfChange
*/
@Override
public void onChange(boolean selfChange) {
System.out.println("短信数据库改变了");
}
}
然后注册这个SmsObserver,即可实现监听短信数据库的变化
// 第一个参数为URI,指定接收哪个ContentProvider发出的通知
// 第二个参数若为true,以第一个参数作为开头(附加任意子路径)的URI上的数据改变了,该ContentObserver也会收到通知
getContentResolver().registerContentObserver(Uri.parse("content://sms"),
true,
new SmsObserver(new Handler());
这里同样需要在清单文件中注册读短信权限
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
上一篇: 集合总结
下一篇: ContentProvider内容提供者