使用ContactsContract API

使用ContactsContract API

自Android 2.0(API Level 5)開始,Android平臺採用了改進後的Contacts API- ContactsContract,用於管理和集成來自多賬戶和多數據來源的聯繫人信息。新的聯繫人API在android.provider.ContactsContract及相關的類來定義,老的API已經廢棄,但仍然可以使用。

  1. Contact API的結構和使用方法

和老的API有所不同的是,在新的Contacts API中,聯繫人數據被安排三個主要的表中:contacts, raw contacts和 data。這種新的結構可以使系統更加容易儲存和管理從多個數據源得到的特定的聯繫人數據。結構如下圖所示:

(圖片來源於:developer.android.com)

1) Data表:儲存所有與RawContract相關具體的信息。表的每一條記錄對應一個特定信息,如:名字、電話,email地址, 頭像和組信息等。每一記錄通過一個mimetype_id字段來表明該行所記錄的數據類型。例如,如果row的數據類型是Phone.CONTENT_ITEM_TYPE,那麼第一個字段應該保存電話號碼,如果數據類型爲Email.CONTENT_ITEM_TYPE,那麼這一記錄的字段應該保存郵件地址。

Data表的字段主要有:

mimetype_id 表示該行存儲的信息的類型
raw_contact_id 表示該行所屬的RawContact
is_primary 多個data數據組成一個raw contact,該字段表示此data是否是其所屬的raw contact的主data,即其display name會作爲raw contact的display name
is_super_primary 該data是否是其所屬的contact的主data,如果is_super_primary爲1則is_primary一定爲1
data1~data15 15個數據字段,對於不同類型的信息,表示不同的含義,ContactsContract.CommomDataKinds類中定義了與常用的數據類型相對應的一些類,這些類中分別定義了相應數據類型中這些字段表示的含義。一般data1表是主信息(如電話,Email地址等),data2表示副信息,data15表示Blob數據。
data_sync1~data_sync4 sync_adapter要用的字段(sync_adapter用於數據的同步,比如你手機中的Gmail帳戶與Google服務器的同步)。
data_version 數據的版本,用於數據的同步。

 

2)RawContact表中的一行存儲Data表中一些數據行的集合及一些其他的信息,表示一個聯繫人某一特定帳戶的信息,比如Facebook或Exchange的一個聯繫人。
當插入一個raw contact或當一個raw contact所屬的一個data改變時,系統會檢查這個raw contact跟其他的raw contact是否可以匹配(比如如果兩個raw contact的data包含相同的電話號碼或名字),如果匹配他們就會被綜合到一起,也就是說他們會屬於同一個cantact,表現爲在RawContact表中他們引用的cantact_id是一樣的。
聯繫人姓名、組織、電話號碼、Email或暱稱的改變會引發raw contact的重新聚合。有兩個方法控制聚合的行爲Aggregaton Mode與ContactsContract.AggregationExceptions。

Aggregaton Mode:
RawContact表中有一個字段aggregation_mode,通過向特定raw contact行中插入這個字段可以修改系統對這個raw contact的聚合行爲,其允許的值如下:AGGREGATION_MODE_DEFAULT:正常模式,允許自動聚合;
AGGREGATION_MODE_DISABLE:不允許聚合;
AGGREGATION_MODE_SUSPENDED:當一個raw contact的aggregation mode修改爲suspended時,如果其已是一個已聚合的contact的一部分,那麼它仍會保持與原來聚合到一起的raw contact的關係,即使它已改變不再跟其他raw contact匹配。AGGREGATIONEXCEPTIONS:

在數據庫中存在一個表:agg_exceptions。通過字段raw_contact_id1、raw_contact_id2、mode存儲兩個raw contact聚合的方法,系統定義的聚合行爲有3個:
TYPE_AUTOMATIC=0  由系統決定聚合行爲,默認值。
TYPE_KEEP_SEPARATE=2  不聚合
TYPE_KEEP_TOGETHER=1  聚合

3) Contact表中的一行表示一個聯繫人,它是RawContact表中的一行或多行的數據的組合,這些RawContact表中的行表示同一個人的不同的帳戶信息。Contact中的數據由系統組合RawContact表中的數據自動生成。不可以直接向這個表中插入數據,當一個raw contact被插入的時候,系統會首先查找Contact表看是否有記錄跟插入的raw contact表示同一個人,如果找到了,則把找到的這個contact的_ID插入raw contact記錄的CONTACT_ID字段,如果沒有找到,則系統自動插入一個Contact記錄並把它的_ID插入新插入的raw contact的CONTACT_ID列。

Contact表中只有TIMES_CONTACTED、LAST_TIME_CONTACTED、STARRED、CUSTOM_RINGTONE、SENE_TO_VOICEMAIL列可更改,這些列的更改會導致相應的raw contact被更改。

數據刪除
    Contacts文檔說:
    Be careful with deleting Contacts! Deleting an aggregate contact deletes all constituent raw contacts.
    The corresponding sync adapters will notice the deletions of their respective raw contacts 
    and remove them from their back end storage.
    刪除一個Contacts,會把它所包含所有raw contacts都刪除掉。
    但是用下面語句居然刪除不了任何Contacts.
    getContentResolver().delete(Contacts.CONTENT_URI, null, null);
    不過在刪除Contacts的所有raw contacts後,Contacts也被刪除了。

如果需要讀取一個聯繫人的信息用CONTENT_LOOKUP_URI代替CONTENT_URI;

如果需要通過電話號碼查找一個聯繫人,用PhoneLookup.CONTENT_FIILTER_URI,這個URI爲這個目的進行了優化;

如果需要通過部分名字的匹配查找,用CONTENT_FILTER_URI;

如果需要通過email,address等信息查找,查找表ContactsContract.Data,結果包含contact ID,名字...

android.provider.ontactsContract.Data類

其定義如下:


 

 public final static class Data implements DataColumnsWithJoins {
        private Data() {}
        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/data";
    //This flag is useful (currently) only for vCard exporter in Contacts app
        public static final String FOR_EXPORT_ONLY = "for_export_only";
    /*Build a CONTENT_LOOKUP_URI style Uri for the parent ContactsContract.Contacts entry of the given ContactsContract.Data entry.*/
        public static Uri getContactLookupUri(ContentResolver resolver, Uri dataUri) {...}
    }


 

Data類定義了CONTENT_URI及CONTENT_TYPE,主要是繼承了DataColumnsWithJoins接口中的字段。DataColumnsWithJoins定義如下:

protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns,
            RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns,
            ContactStatusColumns {
    }

該接口只是綜合了一些接口,其中:

BaseColumns定義了_ID與_COUNT字段,ContentProvider查詢中都要用到的字段。

DataColumns定義了與Data表中字段一一對應的常量

RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns表示RawContact與Contact中的一些字段,定義到此類中以方便訪問。

由於聯繫人的信息都是按類別存儲到了這個Data表中,所以我們要查找聯繫人的信息只需要查這個表。Data類中的Data.CONTACT_ID與Data.RAW_CONTACT_ID分別表示該表項對應的聯繫人在Contact與RawContract表中的ID,我們只需要知道某一個聯繫人的contactId或rawContractId,並根據其查找的數據的類型就可以查到相應類型的信息。

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);

以上代碼根據聯繫人的contractId,並通過設置其MIMETYPE爲Phone.CONTENT_ITEM_TYPE查到了該聯繫人的電話號碼,把Data.CONTACT_ID換爲Data.RAW_CONTACT_ID即可查找相應rawContactId對應的電話號碼。其中用到了Phone.CONTENT_ITEM_TYPE,這是什麼呢?

CommonDataKinds類

在前面講Data表的結構時講到,Data的data1~data15字段用於存儲各類型的數據信息,那麼這15個字段分別表示什麼信息呢?

前面提到了,Data表中有一個mimetype_id字段,通過這個字段關聯mimetypes表表示該行代表的信息類型,因爲Data表中的每一行可以表示如Phone或Address等不同類型的信息,所以對於不同類型的信息,data1~data15這15列表示不同的含義,如果要靠記憶記住這15列對於特定的類型分別表示什麼意義自然不行,於是Google就預定義了一些類,每一個類對應一些預先定義好的數據類型,在每個類中定義了一些語義地、方便記憶的常量,用來對應這15個字段,比如在CommonDataKinds.Email類中有如下定義

public static final String ADDRESS = DATA1;
public static final String DISPLAY_NAME = DATA4;

DATA1與DATA4爲繼承自DataColumns中的常量,在DataColumns中是這樣定義的:

public static final String DATA1 = "data1";
public static final String DATA4 = "data4";

這樣,當於們要查找Email地址時,只需要通過ContactsContract.CommonDataKinds.Email.ADDRESS引用,而不需要知道它是存儲在Data表中的data1列中。

回到上一個問題,上面查詢電話號碼的例子中用到了Phone.CONTENT_ITEM_TYPE,即是表示CommonDataKinds.Phone.CONTENT_ITEM_TYPE,在Phone類中有如下定義

public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone_v2";

vnd.android.cursor.item/phone_v2 就是表示Data表中相應的行存儲的是電話號碼的信息,通過相應類型(如Phone、Email)的CONTENT_ITEM_TYPE,我們就可以指定要查詢的MIMETYPE,即要查詢的數據類型。

其實在上面的例子中還有更簡單的方法:

Cursor phone = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[] { CommonDataKinds.Phone.NUMBER }, CommonDataKinds.Phone.CONTACT_ID + " =? ",
new String[] { String.valueOf(contactId) }, null);

只需把query的第一個參數處傳遞想查找的數據類型的相應的類中的CONTENT_URI就可以了。

 Lookup Key

在新的Contact API中,爲contact引入了lookup key的概念,當你的程序需要保存對聯繫人的引用時,用lookup key而別用row id,lookup key是contacts表中的一列,當你有一個lookup key時,可以這樣構造一個Uri:

Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);

然後用這個Uri查詢:

Cursor c = getContentResolver().query(lookupUri, new String[]{Contacts.DISPLAY_NAME}, ...);
try {
c.moveToFirst();
String displayName = c.getString(0);
} finally {
c.close();
}

用lookup key 而不用row id的原因是因爲row id容易改變,用戶把原先兩個聯繫人合併成一個或因爲同步的問題都會導致row id的改變。lookup key是一個字符串,它由raw contact的標識連接組成。

使用row id會比使用lookup key效率高,你可以同時保存二者,然後聯合二者生成一個lookup uri:

Uri lookupUri = getLookupUri(contactId, lookupKey)

當同時有row id跟lookup key時,系統會優先以row id查詢,如果無查找結果或找到的結果跟lookup key不匹配,則再用lookup key查找。

  參考1:http://developer.android.com/resources/articles/contacts.html  

The new Contacts API introduces the notion of a lookup key for a contact. If your application needs to maintain references to contacts, you should use lookup keys instead of the traditional row ids. You can acquire a lookup key from the contact itself, it is a column on the ContactsContract.Contacts table.

   The reason for this complication is that regular contact row IDs are inherently volatile. Let's say your app stored a long ID of a contact. Then the user goes and manually joins the contact with some other contact. Now there is a single contact where there used to be two, and the stored long contact ID points nowhere.

The lookup key helps resolve the contact in this case. The key is a string that concatenates the server-side identities of the raw contacts. Your application can use that string to find a contact, regardless whether the raw contact is aggregated with others or not.


  示例代碼如下:


package com.memo;
import java.util.ArrayList;
import android.app.ListActivity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class Main extends ListActivity {

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Uri contactsUri=ContactsContract.Contacts.CONTENT_URI;
String[] proj1=new String[]{ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.LOOKUP_KEY};
Cursor curContacts=getContentResolver().query(contactsUri,proj1, null, null, null);
   
//declare a ArrayList object to store the data that will present to the user
ArrayList<String> contactsList=new ArrayList<String>(); 
String allPhoneNo="";
if(curContacts.getCount()>0){
while(curContacts.moveToNext()){
// get all the phone numbers if exist
if(curContacts.getInt(1)>0){
allPhoneNo=getAllPhoneNumbers(curContacts.getString(2));
}
contactsList.add(curContacts.getString(0)+" , "+allPhoneNo);
allPhoneNo="";
}
}
   
// binding the data to ListView 
setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, contactsList));
ListView lv=getListView();
lv.setTextFilterEnabled(true);

}

/**
 * Get all the phone numbers of a specific contact person
 * 
 * @param lookUp_Key lookUp key for a specific contact
 * @return a string containing all the phone numbers
 */
public String getAllPhoneNumbers(String lookUp_Key){
String allPhoneNo="";

// Phone info are stored in the ContactsContract.Data table 
Uri phoneUri=ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String[] proj2={ContactsContract.CommonDataKinds.Phone.NUMBER};
// using lookUp key to search the phone numbers
String selection=ContactsContract.Data.LOOKUP_KEY+"=?";
String[] selectionArgs={lookUp_Key};
Cursor cur=getContentResolver().query(phoneUri,proj2,selection, selectionArgs, null);
while(cur.moveToNext()){
allPhoneNo+=cur.getString(0)+" ";
}
return allPhoneNo;

}

5. 注意事項:

  1)由於要讀取聯繫人信息,所以必須在AndroidMenifest.xml里加入相關uses-permission用於請求使用許可:

<uses-permission android:name="android.permission.READ_CONTACTS" /> 

      參考1:http://developer.android.com/guide/topics/security/security.html 

      參考2:http://www.higherpass.com/Android/Tutorials/Working-With-Android-Contacts/

  2) 怎樣獲取特定聯繫人的電話號碼?由於使用新的API,所以編寫方法與使用舊的API不同。

      在查詢是主要使用到lookUp key 的概念。 

      參考:http://stackoverflow.com/questions/4729551/contactscontract-lookup-phone-number-by-contact-id

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章