2. ContactsContract.java 所有的联系人Uri,与联系人相关数据库字段均在此定义。这里面包含以下要讲的contacts表,raw_contacts表,data表,phone_lookup表的字段及Uri描述。
Table contacts
_id | starred | lookup | photo_id |
|
|
|
|
1 | 0 | 省略 | <null> |
|
|
|
|
2 | 0 | 省略 | <null> |
|
|
|
|
3 | 1 | 省略 | 2366 |
|
|
|
|
starred:标识该账户是否为VIP账户
lookup: 没具体看,也是一个非常重要的字段
photo_id: 引用data表的 _id,
Table raw_contacts
_id | contact_id | account_name | account_type | sync1 |
|
|
|
1 | 1 | Yulei0619@gmail.com | com.google | http://www.google.com/m8/feeds/contacts/yulei0619@gmail.com/base2_property-android/1ac2a39e89c441cc |
|
|
|
2 | 2 | Yulei0619@gmail.com | com.google | 省略 |
|
|
|
3 | 3 | pcsc | com.htc.android.pcsc | 省略 |
|
|
|
contact_id:引用contacts表的_id字段
account_name:指明该联系人是从哪个账户上同步下载下来的。
account_type:邮件帐户的类型
sync1:说明该联系人信息是与哪个网址进行交互(同步联系人信息)
Table data.
_id | raw_contact_id | mimetype_id | data1 | data2 | data15 |
|
1 | 1 | 6 | 南京用友 | 南京用友 | <null> |
|
2 | 1 | 5 | 1-318-296-4252 | 1 | <null> |
|
3 | 2 | 6 | 司道鹏 | 司道鹏 | <null> |
|
4 | 2 | 5 | 1-516-920-8116 | 2 | <null> |
|
5 | 3 | 6 | 子江 | 子江 | <null> |
|
6 | 3 | 5 | 1795113734017562 | 1 | <null> |
|
7 | 3 | 5 | +8613734017562 | 2 | <null> |
|
2366 | 3 | 4 | <null> | <null> | ‰PNG |
|
raw_contact_id: 引用 raw_contacts表的_id字段mimetype_id:vnd.android.cursor.item/photo -> 4 该行 data15代表照片vnd.android.cursor.item/phone_v2 -> 5 该行data1代表电话号码vnd.android.cursor.item/name -> 6说明该行data1代表联系人名称
data15: 存储照片 (Type: Blob)
Table phone_lookup
_id | data_id | raw_contact_id | normalized_number |
1 | 2 | 1 | 25246928131 |
2 | 4 | 2 | 61180296151 |
3 | 6 | 3 | 2657104373115971 |
4 | 7 | 3 | 2657104373168+ |
data_id: 引用 data 表的_id字段
raw_contact_id: 引用raw_contacts表的 _id字段
normalized_number:联系人电话号码的倒序排列
如何将smsmms.db数据库与contacts2.db数据结合起来成为了非常关键的部分
依据联系人的number,查询该联系人的contact_id
public static String getContactId(Context context, String number) { Cursor c = null; try { c = context.getContentResolver().query(Phone.CONTENT_URI, new String[] { Phone.CONTACT_ID, Phone.NUMBER }, null,, null); if (c != null && c.moveToFirst()) { while (!c.isAfterLast()) { if (PhoneNumberUtils.compare(number, c.getString(1))) { return c.getString(0); } c.moveToNext(); } } } catch (Exception e) { Log.e(TAG, "getContactId error:", e); } finally { if (c != null) {c.close(); } } return null; }
Phone.CONTENT_URI, = “content:// com.android.contacts/data/phones”
通过查看源代码发现:该URI主要对应着contacts表,raw_contacts表,data表。这段源码对于刚了解该contact2数据库的人说比较费劲,
“qb.setProjectionMap(distinct ? sDistinctDataProjectionMap : sDataProjectionMap);”是非常重要的线索,它告诉我们会查询哪些字段
在这里尤其需要说明的是:PhoneNumberUtils.compare(String a, String b) 方法
加入我们在手机中把“13466739143”存储为联系人“张三”(存储的联系手机号仅仅是13466739143),如果张三使用飞信给你发短信(我们知道用飞信,电话号码前会自动加12593),用户肯定希望在手机上的未接短信来自张三,而不是有“1259313466739143”被当作一个陌生号码出现
如何将“1259313466739143”和“13466739143”以及可能的“1795113466739143”,“+8613466739143”等等认为是同一个电话号码,这里的PhoneNumberUtils.compare(String a, String b)就起到了非常关键的作用。
有了contact_id之后,我们就可以做很多事情。
1.依据contact_id,去查询该联系人的照片:
public static Bitmap getContactsPhoto(Context context, String contactId) { Cursor c = null; // load icon byte[] icon = null; try { // get contact photo URI Uri refUri = Uri.withAppendedPath(Contacts.CONTENT_URI, contactId); refUri = Uri.withAppendedPath(refUri, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); // get cursor c = context.getContentResolver().query(refUri, new String[] { Photo.PHOTO }, null, null, null); if (c != null && c.moveToFirst()) { icon = c.getBlob(0); } } catch (Exception e) { e.printStackTrace(); } finally { if (c != null) { c.close(); } } if (icon != null) { try { return BitmapFactory.decodeByteArray(icon, 0, icon.length); } catch (OutOfMemoryError e) { e.printStackTrace(); System.gc(); return null; } } return null; }
2. 依据该联系人的contact_id, 去查询该联系人的名字(比如“张三”)
private static String getDisplayName(Context context, String contacts_id) { Cursor c = null; try { c = context.getContentResolver().query(Contacts.CONTENT_URI, new String[] { Contacts.DISPLAY_NAME }, Contacts._ID + "=" + contacts_id, null, null); if (c != null && c.moveToFirst()) { return c.getString(0); } } catch (Exception e) { e.printStackTrace(); } finally { if (c != null) { c.close(); } } return null; }
3. 依据该联系人的contact_id ,查询同一个contact_id有多少个电话号码。
(有可能手机上存了张三2个号码)
public static int getContactNumberCount(Context context, String contactId) { int count = 0; Cursor c = null; try { c = context.getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null, null); if (c != null && c.moveToFirst()) { count = c.getCount(); } } catch (Exception e) { e.printStackTrace(); } finally { if (c != null) { c.close(); } } return count; }
------------------------------------------------------------------------------
如果需要读取一个联系人的信息用CONTENT_LOOKUP_RUI代替CONTENT_URI
如果需要通过电话号码查找一个联系人,用PhoneLookup.CONTENT_FIILTER_URI,这个URI为这个目的进行了优化;
如果需要通过部分名字的匹配查找,用CONTENT_FILTER_URI;
Android中的通信录操作给人的第一感觉就是晕,不知道怎么去用。可以看下这张我画的图:(比较丑,但是地球人都能看懂)
![data1.png data1.png](http://www.android100.org/uploadfile/2013/0303/20130303012157324.png)
![data.png data.png](http://www.android100.org/uploadfile/2013/0303/20130303012202543.png)
通过查询文档: If you need to look up a contact by the phone number, usePhoneLookup.CONTENT_FILTER_URI, which is optimized for this purpose.
我们确定了URI之后就可以开始编写代码了。
public String lookupNameByPhoneNumber(Context context,String phoneNumber) {Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)); Cursor cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); String name = null; if (cursor == null) { name = null; } try { if (cursor.moveToFirst()) { name = cursor.getColumnName(0); } } finally { cursor.close(); } return name;}看完这段代码,你可能想去问,第二步跟第三步跑哪里去了。这两步其实已经被封装在Resoler的query方法中了。如果真要我们编写这两部的话,那我们还不如不用ContentProvider来的轻松。 最后再来说下真机上的通信录模型,真机上的通信录其实就是原生通信录的扩展。增加一些东西。 最重要的一点,原生数据库里有的表,表字段,触发器,视图或索引,在真机上肯定也有。 如果想自己做一个通信录,也肯定要在原生的通信录上扩展,只能增加,不能减少。 想问为什么? 如果你少两张表的话,可能一些系统功能就崩溃了。
这就不符合google的目的了,我提供了一些功能,你可以使用修改,但是你别删除,所以google火了。
添加一条数据
ArrayListops = new ArrayList (); ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()) .build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) .build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType).build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType).build()); // Ask the Contact provider to create a new contact Log.i(TAG, "Selected account: " + mSelectedAccount.getName() + " (" + mSelectedAccount.getType() + ")"); Log.i(TAG, "Creating contact: " + name); try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display warning }
---------------------------------------------------------
Android系统中的联系人也是通过ContentProvider来对外提供数据的,我们这里实现获取所有联系人、通过电话号码获取联系人、添加联系人、使用事务添加联系人。
获取所有联系人
1. Android系统中的联系人也是通过ContentProvider来对外提供数据的
2. 数据库路径为:/data/data/com.android.providers.contacts/database/contacts2.db
3. 我们需要关注的有3张表
raw_contacts:其中保存了联系人id
data:和raw_contacts是多对一的关系,保存了联系人的各项数据
mimetypes:为数据类型
4. Provider的authorites为com.android.contacts
5. 查询raw_contacts表的路径为:contacts
6. 查询data表的路径为:contacts/#/data
这个路径为连接查询,要查询“mimetype”字段可以根据“mimetype_id”查询到mimetypes表中的数据
7. 先查询raw_contacts得到每个联系人的id,在使用id从data表中查询对应数据,根据mimetype分类数据
示例:
//查询所有联系人 public void testGetAll() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.android.contacts/contacts"); Cursor idCursor = resolver.query(uri, new String[] { "_id" }, null, null, null); while (idCursor.moveToNext()) { //获取到raw_contacts表中的id int id = idCursor.getInt(0); //根据获取到的ID查询data表中的数据 uri = Uri.parse("content://com.android.contacts/contacts/" + id + "/data"); Cursor dataCursor = resolver.query(uri, new String[] { "data1", "mimetype" }, null, null, null); StringBuilder sb = new StringBuilder(); sb.append("id=" + id); //查询联系人表中的 while (dataCursor.moveToNext()) { String data = dataCursor.getString(0); String type = dataCursor.getString(1); if ("vnd.android.cursor.item/name".equals(type)) sb.append(", name=" + data); else if ("vnd.android.cursor.item/phone_v2".equals(type)) sb.append(", phone=" + data); else if ("vnd.android.cursor.item/email_v2".equals(type)) sb.append(", email=" + data); } System.out.println(sb); } }
通过电话号码获取联系人
1. 系统内部提供了根据电话号码获取data表数据的功能,路径为:data/phones/filter/*
2. 用电话号码替换“*”部分就可以查到所需数据,获取“display_name”可以获取到联系人显示名
示例:
//根据电话号码查询联系人名称 public void testGetName() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/1111"); Cursor c = resolver.query(uri, new String[] { "display_name" }, null, null, null); while (c.moveToNext()) { System.out.println(c.getString(0)); } }
添加联系人
1. 先向raw_contacts表插入id,路径为:raw_contacts
2. 得到id之后再向data表插入数据,路径为:data
示例:
//添加联系人 ublic void testInsert() { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); ContentValues values = new ContentValues(); // 向raw_contacts插入一条除了ID之外, 其他全部为NULL的记录, ID是自动生成的 long id = ContentUris.parseId(resolver.insert(uri, values)); //添加联系人姓名 uri = Uri.parse("content://com.android.contacts/data"); values.put("raw_contact_id", id); values.put("data2", "FHM"); values.put("mimetype", "vnd.android.cursor.item/name"); resolver.insert(uri, values); //添加联系人电话 values.clear(); // 清空上次的数据 values.put("raw_contact_id", id); values.put("data1", "18600000000"); values.put("data2", "2"); values.put("mimetype", "vnd.android.cursor.item/phone_v2"); resolver.insert(uri, values); //添加联系人邮箱 values.clear(); values.put("raw_contact_id", id); values.put("data1", "zxx@itcast.cn"); values.put("data2", "1"); values.put("mimetype", "vnd.android.cursor.item/email_v2"); resolver.insert(uri, values);
使用事务添加联系人
1. 在添加联系人得时候是分多次访问Provider,如果在过程中出现异常,会出现数据不完整的情况,这些操作应该放在一次事务中
2. 使用ContentResolver的applyBatch(String authority,ArrayList<ContentProviderOperation> operations) 方法可以将多个操作在一个事务中执行
3. 文档位置:
示例:
//使用事务添加联系人 public void testInsertBatch() throws Exception { ContentResolver resolver = getContext().getContentResolver(); ArrayListoperations = new ArrayList (); ContentProviderOperation operation1 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/raw_contacts")) // .withValue("_id", null) // .build(); operations.add(operation1); ContentProviderOperation operation2 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data2", "ZZH") // .withValue("mimetype", "vnd.android.cursor.item/name") // .build(); operations.add(operation2); ContentProviderOperation operation3 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data1", "18612312312") // .withValue("data2", "2") // .withValue("mimetype", "vnd.android.cursor.item/phone_v2") // .build(); operations.add(operation3); ContentProviderOperation operation4 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data1", "zq@itcast.cn") // .withValue("data2", "2") // .withValue("mimetype", "vnd.android.cursor.item/email_v2") // .build(); operations.add(operation4); // 在事务中对多个操作批量执行 resolver.applyBatch("com.android.contacts", operations); }