android通訊錄開發及優化


聯系人

首先需要說明的是,Android系統中的聯系人的存儲並不是僅僅是一張表。信息存儲分為了不同的表,可以按表訪問,同時其設計人員為應用開發人員提供了視圖模式。

下圖是通訊錄的表結構:
通訊錄表結構

查看聯系人SQLITE表結構流程方法博客鏈接

在做通訊錄相關開發之前,首先要添加聯系人相關權限
< 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字段,相互對比,即可獲取新增,刪除和修改信息。然后更新修改信息。
相關資料:

附錄:

raw_contacts表結構:

data表結構:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM