APP跨进程数据通信-访问手机联系人

时间:2021-06-19 21:32:40

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);