Android通訊錄的管理(聯(lián)系人的增刪改查)

Android中的聯(lián)系人存儲是通過ContentProvider實(shí)現(xiàn)的。因此APP對系統(tǒng)通訊錄進(jìn)行操作涉及到ContentProvider接口的使用。

通訊錄存儲常用的數(shù)據(jù)庫表

使用有關(guān)接口前,首先了解一下通訊錄數(shù)據(jù)庫中常用的數(shù)據(jù)庫表:

表名 表用途
contacts 聯(lián)系人表,存儲了實(shí)際的聯(lián)系人姓名,頭像,最后通話時間等信息。
會對實(shí)際的聯(lián)系人數(shù)據(jù)進(jìn)行一定去重。
raw_contacts 實(shí)際的聯(lián)系人數(shù)據(jù)表,每一行是一個單獨(dú)的聯(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是實(shí)際通訊錄中顯示的聯(lián)系人——當(dāng)raw_contacts中存在相同名稱的聯(lián)系人時,系統(tǒng)會將這幾個聯(lián)系人合并。

(例如通過通訊錄添加兩個名字相同的名片,這時系統(tǒng)會提示是否要對這兩個名片進(jìn)行合并。)

data表

1.data表每一行都是一項(xiàng)數(shù)據(jù)(姓名,電話,Email,網(wǎng)址,生日等)。并通過外鍵raw_contacts_idraw_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默認(rèn)存儲blob二進(jìn)制形式的數(shù)據(jù)。

4.那么又如何區(qū)分不同行數(shù)據(jù)的真實(shí)類型呢?是通過data表中mimetype_id列的值(整形)來進(jìn)行區(qū)分。根據(jù)這一列的取值,對data1-14進(jìn)行不同的解析。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 昵稱



在編寫代碼時,實(shí)際傳入的是mimetypes中的字符串參數(shù),而不是ID值。

以上數(shù)據(jù)庫中所有表及字段的定義,都可在android.provider.ContactsContract中找到。

通訊錄存儲的數(shù)據(jù)文件在/data/data/com.android.providers.contacts/databases/目錄下,需要手機(jī)獲取Root權(quán)限。

對通訊錄進(jì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會以用戶提供的手機(jī)號查詢后,再使用標(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)系人某項(xiàng)數(shù)據(jù)(Data中某一項(xiàng))

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ù)組中第一項(xiàng)
    .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項(xiàng)。

向已有聯(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?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容