Android中的聯(lián)系人存儲是通過ContentProvider
實現(xiàn)的骨杂。因此APP對系統(tǒng)通訊錄進行操作涉及到ContentProvider接口的使用肛炮。
通訊錄存儲常用的數(shù)據(jù)庫表
使用有關(guān)接口前,首先了解一下通訊錄數(shù)據(jù)庫中常用的數(shù)據(jù)庫表:
表名 | 表用途 |
---|---|
contacts | 聯(lián)系人表欠橘,存儲了實際的聯(lián)系人姓名逞带,頭像铡原,最后通話時間等信息。 會對實際的聯(lián)系人數(shù)據(jù)進行一定去重由捎。 |
raw_contacts | 實際的聯(lián)系人數(shù)據(jù)表兔综,每一行是一個單獨的聯(lián)系人。 會存在多行對應(yīng)同一個contacts表中條目的情況狞玛。 |
data | 所有聯(lián)系人信息數(shù)據(jù)软驰。通過raw_contact_id 外鍵與raw_contacts 建立聯(lián)系。 |
contacts與raw_contacts的區(qū)分
一個raw_contacts對應(yīng)一個聯(lián)系人心肪,程序中或用戶操作生成新的聯(lián)系人锭亏,就是直接在這個表中插入新條目。
contacts是實際通訊錄中顯示的聯(lián)系人——當(dāng)raw_contacts中存在相同名稱的聯(lián)系人時硬鞍,系統(tǒng)會將這幾個聯(lián)系人合并慧瘤。
(例如通過通訊錄添加兩個名字相同的名片,這時系統(tǒng)會提示是否要對這兩個名片進行合并固该。)
data表
1.data表每一行都是一項數(shù)據(jù)(姓名锅减,電話,Email伐坏,網(wǎng)址怔匣,生日等)。并通過外鍵raw_contacts_id
與raw_contacts
表關(guān)聯(lián)起來桦沉。
2.由1所述每瞒,一個聯(lián)系人根據(jù)情況會有多條data數(shù)據(jù)。數(shù)據(jù)存儲在data1-15這15列中纯露。
例如某一行存儲電話號碼剿骨,那么在表中data1列存儲電話號碼,data2列存儲號碼類型(單位/家庭/組織等)苔埋。
又例如某一行存儲的聯(lián)系人姓名懦砂,那么data1列存儲顯示在界面上的名稱,data2存儲名,data3存儲姓荞膘。
3.依數(shù)據(jù)類型不同罚随,data1-14的含義會不同;data15默認存儲blob二進制形式的數(shù)據(jù)羽资。
4.那么又如何區(qū)分不同行數(shù)據(jù)的真實類型呢淘菩?是通過data表中mimetype_id
列的值(整形)來進行區(qū)分。根據(jù)這一列的取值屠升,對data1-14進行不同的解析潮改。mimetype_id
中數(shù)值與類型的對應(yīng)關(guān)系在mimetypes
表中定義。例如:
_id | mimetypes | 含義 |
---|---|---|
1 | vnd.android.cursor.item/email_v2 | 電子郵件 |
2 | vnd.android.cursor.item/im | 即時通訊 |
3 | vnd.android.cursor.item/nickname | 昵稱 |
在編寫代碼時腹暖,實際傳入的是mimetypes中的字符串參數(shù)汇在,而不是ID值。
以上數(shù)據(jù)庫中所有表及字段的定義脏答,都可在android.provider.ContactsContract
中找到糕殉。
通訊錄存儲的數(shù)據(jù)文件在/data/data/com.android.providers.contacts/databases/
目錄下,需要手機獲取Root權(quán)限殖告。
對通訊錄進行增刪改查
按電話號碼查詢聯(lián)系人
Uri phoneUri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(phone));
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(phoneUri, new String[]{ContactsContract.CommonDataKinds.Phone._ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.CONTACT_ID}, null, null, null);
while (cursor.moveToNext()) {
int id = cursor.getInt(0);
String name = cursor.getString(1);
int contactId = cursor.getInt(2);
if (name.equals(user.getName())) {
deleteList.add(id);
}
}
注意這里使用的URL是ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI
阿蝶,而不是ContactsContract.PhoneLookup.CONTENT_FILTER_URI
。
這是由于PhoneLookup.CONTENT_FILTER_URI
會以用戶提供的手機號查詢后黄绩,再使用標(biāo)準(zhǔn)格式的電話號碼再次查找羡洁,會返回兩個相同的結(jié)果。例如用戶提供了號碼17000000000爽丹,那么程序會先查詢17000000000號碼筑煮,再查詢+86 17000000000,并且兩次查詢都會成功习劫。
查詢通訊錄中所有聯(lián)系人
Uri uri = ContactsContract.Data.CONTENT_URI;
ContentResolver resolver = context.getContentResolver();
Cursor cursorUser = resolver.query(uri, new String[]{ContactsContract.CommonDataKinds.Phone._ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID}, null, null, null);
while( cursorUser.moveToNext()) {
int id = cursorUser.getInt(0); // 按上面數(shù)組的聲明順序獲取
String name = cursorUser.getString(1);
int rawContactsId = cursorUser.getInt(2);
}
刪除聯(lián)系人某項數(shù)據(jù)(Data中某一項)
int id; // data表中對應(yīng)的id值
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data._ID + "=?", new String[]{String.valueOf(d)})
.build());
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
向通訊錄中添加新的聯(lián)系人
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null) // 此處傳入null添加一個raw_contact空數(shù)據(jù)
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // RAW_CONTACT_ID是第一個事務(wù)添加得到的咆瘟,因此這里傳入0,applyBatch返回的ContentProviderResult[]數(shù)組中第一項
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, userName)
.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, phoneNumber)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
.build());
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
其中withValueBackReference接口傳參代表此鍵值是事務(wù)中之前操作得到的結(jié)果诽里,因此需要傳入之前事務(wù)的index值袒餐。由于添加聯(lián)系人是在第一步操作,對應(yīng)結(jié)果數(shù)組的第0項谤狡。
向已有聯(lián)系人中添加新數(shù)據(jù)
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactsId) // 這里關(guān)鍵是傳入正確的raw_contacts_id值
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
.build());
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
參考文檔
通訊錄Android官方文檔灸眼,常用數(shù)據(jù)庫表及相應(yīng)含義
PhoneLookup.CONTENT_FILTER_URI returns twice the same contact
What are the semantics of withValueBackReference?