聯系人
首先需要說明的是,Android系統中的聯系人的存儲並不是僅僅是一張表。信息存儲分為了不同的表,可以按表訪問,同時其設計人員為應用開發人員提供了視圖模式。
下圖是通訊錄的表結構:
在做通訊錄相關開發之前,首先要添加聯系人相關權限:
< uses-permission android:name=”android.permission.READ_CONTACTS” />
< uses-permission android:name=”android.permission.WRITE_CONTACTS” />
數據庫的存儲結構:
- 在聯系人數據庫中,保存的都是一些小的數據表,即與把所有數據保存成一個表不同,它會對聯系人的資料模塊化,然后分成多個表保存。
- android已經替我們准備好了,它在數據庫里面建了一些視圖,視圖就是虛擬表。
- 聯系人的數據庫比較復雜,在聯系人相關應用開發中,一般也不直接通過數據庫字段來操作,主要用視圖(指定的Uri)來操作。
- android也提供了很多接口,通過ContentResolver().query方法,傳入不同的URI即可訪問相應的數據集。
一個聯系人信息的存儲
- 在聯系人數據庫里面聯系人和電話號碼是分別存在兩個表里面的,因為存在一個聯系人擁有幾個號碼的情況,所以android為聯系人和手機號碼分別單獨創建了相應的視圖。
- 聯系人信息的視圖里面只保存與聯系人相關的資料,例如姓名,是否有手機號碼等。
- 手機號碼資料則是每一個電話號碼為一條記錄,如果有一個聯系人有3個號碼,則里面會出現3個該聯系人的記錄,號碼分別為他的三個號碼。
不同視圖URL
如果是需要讀取聯系人信息,使用的URI為:ContactsContract.Contacts.CONTENT_URI
如果是需要讀取手機號碼信息, 使用的URI為:ContactsContract.CommonDataKinds.Phone.CONTENT_URI
ContentResolver.query Method解析
ContentResolver.query函數的原型,query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
- projection:是需要讀取的字段
- selection:是數據檢索的條件
- selectionArgs:是數據檢索條件的參數
- sortOrder:是排序的字段
解釋一下:
假如一條sql語句如下:select * from anyTable where var=’const’
那么anyTable就是uri,*就是projection,selection是“var=?”,selectionArgs寫成這樣:new String[]{‘const‘}至於最后一個就簡單了,就是排序方式。
只讀取自己需要的信息,減少讀取的信息,可以減少讀取時間
public static List<FriendItem> getContactsMultiPhoneNumber(Context context) {
//定義常量,節省重復引用的時間
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
String ID = ContactsContract.Contacts._ID;
String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
String HAS_PHONE_NUMBER = ContactsContract.Contacts.HAS_PHONE_NUMBER;
//臨時變量
String contactId;
String displayName;
Cursor phoneCursor;
//生成ContentResolver對象
ContentResolver contentResolver = context.getContentResolver();
// 獲取手機聯系人
Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/contacts"), null, null, null, null);
List<FriendItem> friendList = new ArrayList<>();
// 無聯系人直接返回
if (!cursor.moveToFirst()) {//moveToFirst定位到第一行
return null;
}
do {
// 獲得聯系人的ID:String類型 列名--》列數--》列內容
contactId = cursor.getString(cursor.getColumnIndex(ID));
// 獲得聯系人姓名:String類型
displayName = cursor.getString(cursor.getColumnIndex(DISPLAY_NAME));
// 查看聯系人有多少個號碼,如果沒有號碼,返回0
int phoneCount = cursor.getInt(cursor.getColumnIndex(HAS_PHONE_NUMBER));
FriendItem item;
List<String> phoneList = new ArrayList<>();
if (phoneCount <= 0) {
continue;
} else if (phoneCount == 1) {
//僅有一個聯系號碼
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + contactId, null, null);
if (!phoneCursor.moveToFirst()) {
continue;
} phoneList.add(StringUtil.removeBlank(phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER))));
item = new FriendItem(displayName, phoneList, false);
} else {
// 有多個聯系號碼
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + contactId, null, null);
if (!phoneCursor.moveToFirst()) {
continue;
}
do {
phoneList.add(StringUtil.removeBlank(
phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER))
));
} while (phoneCursor.moveToNext());
item = new FriendItem(displayName, phoneList, false);
}
item.setMainPhoneNumber(phoneList.get(0));
friendList.add(item);
} while (cursor.moveToNext());
return friendList;
}
上述代碼所實現的效果是讀取聯系人姓名,手機號(如果有多個手機號,將多個手機號都讀出來)。因為聯系人信息和手機號信息是在不同的數據庫表中,所以先通過Contact視圖讀出用戶信息,然后再在phoneNumber視圖中讀出手機號。思路很自然。該代碼經過實際測試,1000個聯系人耗時約為20秒左右,實際使用中效率無法滿足需求。
對上述代碼的優化過程:
流程拆分和優化
將上面的讀取信息的兩個過程拆分開,與用戶界面結合。初始顯示所有用戶時不顯示手機號,只顯示用戶名,當用戶列表中某個用戶名被選中時,再去尋找該用戶對應的手機號。這樣就節省了大量無用用戶手機號的讀取時間。經過實際測試,經過這樣處理之后,1000個聯系人的預讀取時間在300ms左右,單獨獲取手機號的時間可以不在用戶能夠感知到的范圍。
獲取簡略用戶列表
public static List<FriendItem> getBriefContactInfor(Context context) {
//定義常量,節省重復引用的時間
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
String ID = ContactsContract.Contacts._ID;
String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
String HAS_PHONE_NUMBER = ContactsContract.Contacts.HAS_PHONE_NUMBER;
//臨時變量
String contactId;
String displayName;
//生成ContentResolver對象
ContentResolver contentResolver = context.getContentResolver();
// 獲取手機聯系人
Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/contacts"), null, null, null, null);
List<FriendItem> friendList = new ArrayList<>();
// 無聯系人直接返回
if (!cursor.moveToFirst()) {//moveToFirst定位到第一行
return null;
}
do {
// 獲得聯系人的ID:String類型 列名--》列數--》列內容
contactId = cursor.getString(cursor.getColumnIndex(ID));
// 獲得聯系人姓名:String類型
displayName = cursor.getString(cursor.getColumnIndex(DISPLAY_NAME));
// 查看聯系人有多少個號碼,如果沒有號碼,返回0
int phoneCount = cursor.getInt(cursor.getColumnIndex(HAS_PHONE_NUMBER));
FriendItem item;
item = new FriendItem(displayName, null, false);
item.setContactId(contactId);
item.setPhoneCount(phoneCount);
friendList.add(item);
} while (cursor.moveToNext());
for (int i = 0; i < friendList.size(); i++) {
friendList.get(i).setPinyin(PinYin.getPinYin(friendList.get(i).getName()).toLowerCase());
}
Collections.sort(friendList);
return friendList;
}
獲取當個用戶的手機號
//根據cotact_id來獲取該聯系人的手機號
public static FriendItem getDetailFromContactID(Context context, FriendItem item) {
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
Cursor phoneCursor;
ContentResolver contentResolver = context.getContentResolver();
List<String> phoneList = new ArrayList<>();
if (item.getContactId() == null) {
return item;
}
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + item.getContactId(), null, null);
if (!phoneCursor.moveToFirst()) {
return item;
}
do {
String temp = StringUtil.normalizePhone(phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER)));
if (temp != null) {
phoneList.add(temp);
}
} while (phoneCursor.moveToNext());
item.setPhoneNumber(phoneList);
return item;
}
預讀取和修改監控
考慮到通訊錄的實際變動比較少,可以先對通訊錄的信息進行預先讀取之后,自行保存下來,或者在app登錄時預先讀取(有一定用戶隱私的爭議,這里是個思路)。在每次實際的使用前,先檢查,當前的通訊錄與保存下來的通訊錄之間有沒有信息改動。檢測的過程是,在raw_contacts表中有version字段。在預讀取時將通訊錄的所有用戶以及version保存下來,然后使用通論錄信息時,獲取當前用戶以及version字段,相互對比,即可獲取新增,刪除和修改信息。然后更新修改信息。
相關資料: