1. 简述
在实际开发中,常常需要进行不同应用程序之间的数据通信,例如读取联系人列表等等,ContentProvider就是Android提供的用于实现不同进程之间进行数据通信的类。
ContentProvider的作用是对外提供对本应用的数据进行“增删改查”的接口,而后在其它程序可通过ContentResolver类访问提供的接口,从而实现跨应用数据通信。
2. ContentProvider类
首先,ContentProvider与其它几个组件一样也是一个抽象类,使用时必须实现一些方法,并且也需要在manifest中进行注册。
另外,如果学过SQLite的操作,你会发现增删改查几个方法与SQLite数据库的几个操作方法极为相似。
注册:
<provider
android:name=".MyContentProvider"
android:authorities="com.studying.myprovider"
android:enabled="true"
android:exported="true" />
PS:provider标签写在application中,与activity同级。
实现的方法:
boolean onCreate()
:在创建ContentProvider时会调用,在这里完成一些初始化的操作,注意要返回true。
Uri insert(Uri uri, ContentValues values)
:添加方法。
关于Uri:参数中的uri是数据接收方指定哪一个ContentProvider的依据,格式为"content://authorities[/path]",authorities即manifest中注册provider时的属性,命名规则类似Java的包名,path则为路径,可以没有,例如"content://com.studying.myprovider"。(关于Uri的解析后面会介绍)
第二个参数values则是插入的值包,其中key值需与数据库中的列名保持一致。另外,insert()方法的返回值为一个Uri,可以在传入的Uri后加上成功插入的记录的ID值作为返回的Uri,可用于判断是否插入成功。添加ID与取出ID的方法为:
ContentUris.withAppendedId(Uri contentUri, long id)// 把id追加到contentUri后面
ContentUris.parseId(Uri uri)// 将id取出
int delete(Uri uri, String selection, String[] selectionArgs)
:selection为条件,selectionArgs为条件值,返回值为受操作影响的行数,例如删除了1行,则返回1。
int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
:返回值同delete()方法。
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
:projection为查询的列,sortOrder为排序方法,query()方法返回一个Cursor对象。
String getType(Uri uri)
:返回数据的MIME类型,这个方法一般用不到,详情可参考:ContentProvider数据库共享之——MIME类型与getType()
3. ContentResolver类:
应用程序提供数据共享的接口时使用ContentProvider类,而需要访问其它应用共享的数据时,则需要使用ContentResolver类。
获取ContentResolver对象的方法:在Activity中直接getContentResolver()方法即返回一个ContentResolver对象。
使用方法非常简单,调用ContentResolver对象中的insert()等方法会调用Uri匹配到的ContentProvider中的相应方法。
简单例子:
ContentValues values = new ContentValues();
values.put("name", name);
values.put("age", age);
values.put("sex", sex);
Uri returnUri = resolver.insert(Uri.parse("content://com.studying.myprovider"), values);
long newItemId = ContentUris.parseId(returnUri);
Toast.makeText(this, "添加成功,新增加的学生ID为" + newItemId, Toast.LENGTH_SHORT).show();
4. URI的解析
URI即Uniform Resource Identifier 统一资源标识符,在这里可以标识访问哪一个ContentProvider,除此之外,还可以传递参数,以及通过匹配UriMatcher类制定的不同匹配规则进行相应处理。
(1)UriMatcher类:
UriMatcher类可以制定URI的匹配规则,然后可以通过在使用resolver对象时传入不同格式的URI,使ContentProvider类做出不同的处理。
a)创建匹配规则:matcher.addURI(authorities, path, code),path为路径,可使用#表示任意数字,*表示任意字符;code则为匹配码。
b)在对应方法中匹配:int code = matcher.match(uri),返回的code即为匹配码。
简单例子:
首先在ContentProvider的onCreate()方法中制定规则:
matcher = new UriMatcher(UriMatcher.NO_MATCH);// 当所有匹配情况都无法匹配到时,则返回UriMatcher.NO_MATCH
matcher.addURI("com.studying.myprovider", "test1/#", 1001);
matcher.addURI("com.studying.myprovider", "test2/*", 1002);
然后在处理方法中做不同处理:
int columnCount = 0;
switch (matcher.match(uri)) {
case 1001:
Log.e("TAG", "匹配成功!匹配形式为:test1 + 任意数字");
break;
case 1002:
Log.e("TAG", "匹配成功!匹配形式为:test2 + 任意字符串");
break;
default:
columnCount = db.delete(TABLE_NAME, selection, selectionArgs);
Log.e("TAG", "删除成功!");
break;
}
最后通过ContentResolver传入需要的Uri进行使用:
ContentResolver resolver = getContentResolver();
resolver.delete(Uri.parse("content://com.studying.myprovider/test1/132"), null, null); // 匹配到1001
resolver.delete(Uri.parse("content://com.studying.myprovider/test2/1ac2"), null, null); // 匹配到1002
resolver.delete(Uri.parse("content://com.studying.myprovider/12"), null, null); // 匹配不到,则为UriMatcher.NO_MATCH
(2)Uri自带解析方法
除了UriMatcher类,还可以通过Uri类自带的解析方法进行传值,传递参数的方法是:在Uri末尾加上“?”,而后后面加上参数,多个参数之间以“&”连接,例如"content://com.studying.myprovider?name=Tim&age=22"。
在Uri传递到ContentProvider之后,通过以下方法取出需要的部分:
uri.getAuthorities():获取authorities部分。
uri.getPath():获取path部分。
uri.getQuery():获取“?”后面的全部字符串。
uri.getQueryParameter(parameterName):传入相应参数名,获取该参数值。例如String name = uri.getQueryParameter("name");
5. 访问手机短信箱
访问短信箱的方式非常简单,跟上面例子的步骤基本一致,只需要额外到manifest中添加访问短消息的权限即可。
读取权限:android.permission.READ_SMS
写入:android.permission.WRITE_SMS
Uri:
短信箱(全部短消息,包括发送的、接收的以及草稿):content://sms
收件箱:content://sms/inbox
发件箱:content://sms/sent
草稿箱:content://sms/draft
ContentResolver resolver = getContentResolver();
Uri smsUri = Uri.parse("content://sms/inbox");
Cursor c = resolver.query(smsUri, null, null, null, null);
while (c != null && c.moveToNext()) {
// 3和13分别是号码和短消息内容所在的列的索引
Log.e("TAG", c.getString(3) + " " + c.getString(13));
}
6. 读取联系人列表
与短消息不同,安卓中存储联系人的方式相对比较复杂,联系人的姓名和号码是分开存储的。简单地理解,可以想象成是数据库的形式,姓名和号码分别存放在两张数据表中,而互相之间通过一个唯一的ID值进行标识。因此,读取联系人需要先获取姓名和ID,再通过ID去获取号码。
权限:android.permission.READ_CONTACTS
姓名所在的ContentProvider的URI:ContactsContract.Contacts.CONTENT_URI
姓名列的列名常量:ContactsContract.Contacts.DISPALY_NAME
ID列的列名常量:ContactsContract.Contacts._ID (PS:切勿遗漏了下划线)
号码所在ContentProvider的URI:ContactsContract.CommonDataKinds.Phone.CONTENT_URI
号码外键列的列名常量:ContactsContract.CommonDataKinds.Phone.CONTACT_ID
号码列的列名常量:ContactsContract.CommonDataKinds.Phone.NUMBER
// 首先获取姓名和ID
ContentResolver resolver = getContentResolver();
Cursor nameCursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while (nameCursor != null && nameCursor.moveToNext()) {
String name = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String _id = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.Contacts._ID));
// 再通过ID获取相应的号码
String selections = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?";
Cursor numberCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, selections, new String[] {_id}, null);
String result = "";
while (numberCursor != null && numberCursor.moveToNext()) {
String number = numberCursor.getString(numberCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
result += name + " " + number + " ";
}
Log.e("RESULT", result);
}
7. 添加联系人
读取联系人时需要分开读取,那么写入当然也要分开写入。首先向一个存储了一些其它数据的ContentProvider中插入一个空数据,从而获取到一个新的ID,再通过这个ID分别插入姓名和号码(因为每次插入时都需要指定插入数据的类型,因此需要分开插入)。
权限:android.permission.WRITE_CONTACTS
Uri:
获取ID的URI:ContactsContract.RawContacts.CONTENT_URI
插入数据的URI:ContactsContract.Data.CONTENT_URI
需要使用的常量:
姓名的列名:ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
ID的列名:ContactsContract.Data.RAW_CONTACT_ID
电话号码的列名:ContactsContract.CommonDataKinds.Phone.NUMBER
指定MIME类型的列名:ContactsContract.Data.MIMETYPE
姓名的MIME类型:ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
电话号码的MIME类型:ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
电话号码类型的列名:ContactsContract.CommonDataKinds.Phone.TYPE
手机号码:ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
住宅号码:ContactsContract.CommonDataKinds.Phone.TYPE_HOME
PS:常量非常多,要留意其所在的包,共用的一些常量位于ContactsContract.Data包下,例如ID、数据类型等,姓名相关的则在ContactsContract.CommonDataKinds.StructuredName包下,电话号码相关的则在ContactsContract.CommonDataKinds.Phone包下,理清之后就容易记住了。
ContentResolver resolver = getContentResolver();
// 获取一个新的ID
ContentValues values = new ContentValues();
Uri idUri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
long id = ContentUris.parseId(idUri);
// 插入姓名
values.put(ContactsContract.Data.RAW_CONTACT_ID, id);
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "Jay");
// 需要指定姓名的数据类型,也就是getType()方法将来返回的值
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
resolver.insert(ContactsContract.Data.CONTENT_URI, values);
// 插入号码
values.clear();
values.put(ContactsContract.Data.RAW_CONTACT_ID, id);
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, "13511223344");
// 指定联系号码的类型,如手机号码、住宅号码等
values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
resolver.insert(ContactsContract.Data.CONTENT_URI, values);