概述
本文主要解讀Android系統(tǒng)聯(lián)系人相關的知識,以及展示聯(lián)系人相關的操作實例。參考Android官方API。由于Contact在API 5的時候廢棄,因為其只支持單賬號聯(lián)系人。所以本文就直接介紹ContactContract類
ContactsContract
ContactsContract是什么呢?官方文檔是這樣描述的:
The contract between the contacts provider and applications. Contains definitions for the supported URIs and columns. These APIs supersede ContactsContract.Contacts.
聯(lián)系人應用和提供者包括自定義URL可字段都被新的ContactsContractAPI取代。也就是說新的API支持多個URL也就是多賬號,是Contact類的升級版。
ContactsContract定義了可擴展的聯(lián)系人關聯(lián)數(shù)據(jù)庫,聯(lián)系人信息存儲為三層表模型
-
ContactsContract.Data:表中一行可以存儲任何個人數(shù)據(jù),例如手機號或者郵箱地址。并且表中的所有數(shù)據(jù)皆為開放的。表中有一些常規(guī)的預定字段,當然任何應用可以根據(jù)自身需求去定義自己數(shù)據(jù)字段。 -
ContactsContract.RawContacts:這個表表示用戶單個賬號下面的聯(lián)系人,比如用戶的Gmail郵箱內的聯(lián)系人。 -
ContactsContract.Contact:此表存放著描述同一個人的一個或者多個RowContact(賬號)的聚合數(shù)據(jù)。當單個賬號數(shù)據(jù)修改后,此表會根據(jù)需要更新對應的聚合聯(lián)系人。
其它表
-
ContactsContract.Groups:其中包含有關Gmail聯(lián)系人組等原始聯(lián)系人群組的信息。當前的API不支持跨多個賬戶的組的概念。 -
ContactsContract.StatusUpdates:其中包含社交狀態(tài)更新,包括IM可用性。 -
ContactsContract.AggregationExceptions:用于手動聚合和分解原生數(shù)據(jù)。 -
ContactsContract.Settings:其中包含賬號和組的可見性和同步設置。 -
ContactsContract.SyncState: 其中包含代表同步適配器維護的自由格式數(shù)據(jù)。 -
ContactsContract.PhoneLookup:用于緊急呼叫者的ID查找。
ContactsContact.Contacts
聯(lián)系人表的常熟,其中包含代表同一人的原始聯(lián)系人的每個匯總的記錄
操作
Insert
無法明確的創(chuàng)建聯(lián)系人。當一條聯(lián)系人數(shù)據(jù)插入的時候,provider首先去查找是否存在同一個聯(lián)系人。如果找到,會從聚合表中獲取聯(lián)系人的CONTACT_ID。如果沒有找到匹配的聯(lián)系人,provider會自動的插入一個新的聯(lián)系人,并將新的聯(lián)系人插入原始的聯(lián)系人數(shù)據(jù)表中。
update
當某個確定的列被修改如: TIMES_CONTACTED, LAST_TIME_CONTACTED, STARRED, CUSTOM_RINGTONE, SEND_TO_VOICEMAIL. 更改聯(lián)系人表的以上字段都會更改所有構成聯(lián)系人的原始數(shù)據(jù)。
Query
- 如果只是閱讀個別聯(lián)系人,可以使用
CONTENT_LOOKUP_URI去代替CONTENT_URI。 - 如果你要根據(jù)電話號碼來查找聯(lián)系人,使用
PhoneLookup.CONTENT_FILTER_URI,對此進行了優(yōu)化 - 如果你要根據(jù)部分名稱查詢聯(lián)系人,例如某些過濾條件,可以使用
CONTENT_FILTER_URI - 如果你想根據(jù)某些數(shù)據(jù)元素如郵箱地址,昵稱等查詢聯(lián)系人,可以對
ContactsContract.Data表進行查詢,該表中包含聯(lián)系人ID,姓名等。
ContactsContract.Data
表中的常量包含與聯(lián)系人綁定的數(shù)據(jù)點。且每一行的數(shù)據(jù)表中存放著單個一些列的聯(lián)系人信息(比如手機號)和他相關的數(shù)據(jù)如(家庭電話或者是工作電話)。
數(shù)據(jù)類型
數(shù)據(jù)表可以容納任何種類的聯(lián)系數(shù)據(jù)。存儲在一個特定行的數(shù)據(jù)由MIMETYPE值指定,該值決定了數(shù)據(jù)存放在通用的數(shù)據(jù)表DATA1到DATA15行中。比如,如果數(shù)據(jù)類型是 Phone.CONTENT_ITEM_TYPE則DATA1列存放手機號,如果數(shù)據(jù)類型是 Email.CONTENT_ITEM_TYPE則DATA1存放為郵箱地址。同步的適配器和應用可以定義他們自己的數(shù)據(jù)類型。
ContactsContract預設了很少一部分數(shù)據(jù)類型,例如:ContactsContract.CommonDataKinds.Phone, ContactsContract.CommonDataKinds.Email等等。為了方便起見,為DATA1起了別名。Phone.NUMBER與DATA1是相同的意思。
DATA1位索引列,用于預期在查詢選擇中最常用的數(shù)據(jù)元素。例如在代表電子郵件地址的DATA1行的情況下,應用于郵件地址本身,而DATA2等可以用于諸如電子郵件類型的輔助信息。
按照慣例,DATA15用于存儲BLOB(二進制數(shù)據(jù))
給定賬戶類型的同步適配器必須正確的處理相應原始聯(lián)系人中使用的每種數(shù)據(jù)類型。否則可能導致數(shù)據(jù)丟失或者損壞。
同樣的你不能為已有的賬號類型引入新的數(shù)據(jù)類型。例如,你給谷歌賬號的聯(lián)系人數(shù)據(jù)表行中中新增一個數(shù)據(jù)類型“favorite song”,它將不會同步到服務器上面,因為谷歌同步適配器并不能解析這種數(shù)據(jù)類型。所以新的數(shù)據(jù)類型應該去建立新的賬戶類型或者新的同步適配器。
批量操作
可以使用傳統(tǒng)的插入(Uri,ContentValues),update(Uri,ContentValues,String,String [])和delete(Uri,String,String [])方法來插入/更新/刪除數(shù)據(jù)行,但是基于在幾乎所有情況下,批處理ContentProviderOperation將被證明是一個更好的選擇。批處理中的所有操作都在單個事務中執(zhí)行,這確保原始聯(lián)系人的電話端和服務器端狀態(tài)始終保持一致。此外,基于批處理的方法效率更高:不僅數(shù)據(jù)庫操作在單個事務中執(zhí)行時更快,而且還向內容提供者發(fā)送一批命令可以節(jié)省大量時間在進程和內容提供商運行的過程。
使用批處理操作的另一面是大批量可能長時間鎖定數(shù)據(jù)庫,從而阻止其他應用程序訪問數(shù)據(jù)并潛在地導致ANR(“應用程序無響應”對話框)。)
為了避免數(shù)據(jù)庫的這種鎖定,請確保在批處理中插入“產(chǎn)生點”。產(chǎn)生點向內容提供者指出,在執(zhí)行下一個操作之前,它可以提交已經(jīng)進行的更改,產(chǎn)生其他請求,打開另一個事務并繼續(xù)處理操作。屈服點不會自動提交事務,但只有在數(shù)據(jù)庫上有其他請求等待時。通常,同步適配器應在批次中的每個原始接觸操作序列的開始處插入屈服點。
操作符
Insert
可以使用傳統(tǒng)插入(Uri,ContentValues)方法插入單個數(shù)據(jù)行。應該始終以批次的形式插入多個行。
例如:
ContentValues values = new ContentValues();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
values.put(Phone.NUMBER, "1-800-GOOG-411");
values.put(Phone.TYPE, Phone.TYPE_CUSTOM);
values.put(Phone.LABEL, "free directory assistance");
Uri dataUri = getContentResolver().insert(Data.CONTENT_URI, values);
同樣的可以使用ContentProviderOperations:
ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValue(Data.RAW_CONTACT_ID, rawContactId)
.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, "1-800-GOOG-411")
.withValue(Phone.TYPE, Phone.TYPE_CUSTOM)
.withValue(Phone.LABEL, "free directory assistance")
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
Update
就像插入一樣,更新可以逐個或者批處理,批處理模式是首選方法:
ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI)
.withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
.withValue(Email.DATA, "somebody@android.com")
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
Delete
就像插入和更新一樣,刪除可以使用delete(Uri,String,String [])方法或使用ContentProviderOperation完成:
ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI)
.withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
Query
查找給定聯(lián)系人的給定類型的所有數(shù)據(jù)
Cursor c = getContentResolver().query(Data.CONTENT_URI,
new String[] {Data._ID, Phone.NUMBER, Phone.TYPE, Phone.LABEL},
Data.CONTACT_ID + "=?" + " AND "
+ Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'",
new String[] {String.valueOf(contactId)}, null);
查找給定聯(lián)系人的給定類型的所有原始數(shù)據(jù)
Cursor c = getContentResolver().query(Data.CONTENT_URI,
new String[] {Data._ID, Phone.NUMBER, Phone.TYPE, Phone.LABEL},
Data.RAW_CONTACT_ID + "=?" + " AND "
+ Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'",
new String[] {String.valueOf(rawContactId)}, null);
插入通訊錄實例
插入demo
/**
* 插入本地聯(lián)系人數(shù)據(jù)庫
* 名片夾的插入地方目前全部是以新增模式保存通訊錄,待放出名片夾功能時需要做是否是更新操作的判斷
*/
public static int insertContact(Context context, List<CardCaseBean> list, boolean... action) {
boolean isUpdate = false;
if (action.length > 0) {
if (action[0]) {
isUpdate = action[0];
}
}
if (list == null || list.size() < 0) {
return 0;
}
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
int rawContactInsertIndex = 0;
int resutl = 0;
for (CardCaseBean card : list) {
rawContactInsertIndex = ops.size();
if (isUpdate) {
ops.add(ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
} else {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
}
//插入姓名
if (!TextUtils.isEmpty(card.name)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, card.name)
.build());
}
//插入電話
if (!TextUtils.isEmpty(card.mobile)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, card.mobile)
.build());
}
//插入傳真
if (!TextUtils.isEmpty(card.fax)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, card.fax)
.build());
}
//插入Email
if (!TextUtils.isEmpty(card.email)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, card.email)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
.build());
}
//插入工作網(wǎng)址
if (!TextUtils.isEmpty(card.web)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Website.URL, card.web)
.withValue(ContactsContract.CommonDataKinds.Website.TYPE, ContactsContract.CommonDataKinds.Website.TYPE_WORK)
.build());
}
//插入地址
if (!TextUtils.isEmpty(card.address)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, card.address)
.build());
}
//插入部門
if (!TextUtils.isEmpty(card.department)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, card.department)
.build());
}
//插入職務
if (!TextUtils.isEmpty(card.jobTitle)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.TITLE, card.jobTitle)
.build());
}
//插入公司
if (!TextUtils.isEmpty(card.company)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, card.company)
.withYieldAllowed(true)
.build());
}
//插入備注【公司簡介】
if (!TextUtils.isEmpty(card.note)) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Note.DATA1, card.note)
.withYieldAllowed(true)
.build());
}
}
// 真正添加
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
if (results != null && results.length > 0) {
for (ContentProviderResult result : results) {
SoutUtils.out("URI:" + result.uri, "count:" + result.count);
}
resutl++;
}
} catch (RemoteException | OperationApplicationException e) {
e.printStackTrace();
}
return resutl;
}
插入前要進行一些權限的判斷以及開啟
/***
* 執(zhí)行插入用戶的通訊錄
*
* @param activity:當前的Activity
* @param items:待插入的數(shù)據(jù)
* @param isUpdate:是否是更新通訊錄
*/
private static boolean executeInsertToAddressBook(final Activity activity, List<CardCaseBean> items, boolean isUpdate) {
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
//用戶拒絕授予權限并且不再提示
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_CONTACTS)) {
CommonMsgDialog.Builder commonMsgDialog = new CommonMsgDialog.Builder(activity)
.setMessage("沒有寫入通訊錄的權限").setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).setPositiveButton("開啟", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_CONTACTS},
REQUEST_WRITE_CONSTACTS_PERMISSIONS);
}
});
commonMsgDialog.create().show();
return false;
}
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_CONTACTS},
REQUEST_WRITE_CONSTACTS_PERMISSIONS);
return false;
}
int result = UserUtils.insertContact(activity, items, isUpdate);
boolean isSuccess = result > 0;
if (isSuccess) {
if (isUpdate) {
Util.showShortToast(activity, "更新成功");
} else {
Util.showShortToast(activity, "已存入通訊錄");
}
} else {
Util.showShortToast(activity, "存入失敗");
}
return isSuccess;
}
獲取所有聯(lián)系人常用字段數(shù)據(jù)
/**
* 獲取所有聯(lián)系人的姓名、手機號、公司、職務、地址、郵箱
*/
public static List<CardCaseBean> getAllContact(Context context) {
//讀取通訊錄的全部的聯(lián)系人
//需要先在raw_contact表中遍歷id,并根據(jù)id到data表中獲取數(shù)據(jù)
//uri = content://com.android.contacts/contacts
List<CardCaseBean> list = new ArrayList<>();
Uri uri = Uri.parse("content://com.android.contacts/contacts"); //訪問raw_contacts表
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{ContactsContract.Contacts.Data._ID}, null, null, null); //獲得_id屬性
while (cursor.moveToNext()) {
CardCaseBean contact = new CardCaseBean();
int id = cursor.getInt(0);//獲得id并且在data中尋找數(shù)據(jù)
uri = Uri.parse("content://com.android.contacts/contacts/" + id + "/data"); //如果要獲得data表中某個id對應的數(shù)據(jù),則URI為content://com.android.contacts/contacts/#/data
Cursor cursor2 = resolver.query(uri, new String[]{ContactsContract.Contacts.Data.DATA1, ContactsContract.Contacts.Data.MIMETYPE}, null, null, null); //data1存儲各個記錄的總數(shù)據(jù),mimetype存放記錄的類型,如電話、email等
while (cursor2.moveToNext()) {
String data = cursor2.getString(cursor2.getColumnIndex("data1"));
if (isHasValue(data)) {
if (cursor2.getString(cursor2.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) { //如果是名字
contact.name = data;
} else if (cursor2.getString(cursor2.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) { //如果是電話
contact.mobile = data;
} else if (cursor2.getString(cursor2.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) { //如果是email
contact.email = data;
} else if (cursor2.getString(cursor2.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) { //如果公司
contact.company = data;
}
}
}
Cursor cursor3 = resolver.query(uri, new String[]{ContactsContract.Contacts.Data.DATA4, ContactsContract.Contacts.Data.MIMETYPE}, null, null, null); //data4 獲取公司地址以及職務
while (cursor3.moveToNext()) {
String data = cursor3.getString(cursor3.getColumnIndex("data4"));
if (isHasValue(data) && cursor3.getString(cursor3.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
contact.jobTitle = data;
}
}
Cursor cursor4 = resolver.query(uri, new String[]{ContactsContract.Contacts.Data.DATA9, ContactsContract.Contacts.Data.MIMETYPE}, null, null, null); //data4 獲取公司地址以及職務
while (cursor4.moveToNext()) {
String data = cursor4.getString(cursor4.getColumnIndex("data9"));
if (isHasValue(data) && cursor4.getString(cursor4.getColumnIndex(ContactsContract.Data.MIMETYPE)).equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
contact.address = data;
}
}
list.add(contact);
}
return list;
}