Android通信錄數據庫操做(轉)

能夠參考  這個博客 http://www.uml.org.cn/mobiledev/201110121.asp
對contact2.db中的幾個比較經常使用的表進行總結:
包括 contacts表,raw_contacts表,data表,phone_lookup表

但願你們參照源代碼:

1. ContactsProvider2.java (實現了ContentProvider)html

2. ContactsContract.java 全部的聯繫人Uri,與聯繫人相關數據庫字段均在此定義。這裏麪包含如下要講的contacts表,raw_contacts表,data表,phone_lookup表的字段及Uri描述。java

Table contactsandroid

_idsql

starred數據庫

lookupwindows

photo_id網絡

 

 

 

 

1app

0eclipse

 省略ide

<null>

 

 

 

 

2

0

 省略

<null>

 

 

 

 

3

1

 省略

2366

 

 

 

 

 

starred:標識該帳戶是否爲VIP帳戶

lookup: 沒具體看,也是一個很是重要的字段

photo_id 引用data表的 _id

 

Table raw_contacts

_id

contact_id

account_name

account_type

sync1

 

 

 

1

1

Yulei0619@gmail.com

com.google

http://www.google.com/m8/feeds/contacts/yulei0619@gmail.com/base2_property-android/1ac2a39e89c441cc

 

 

 

2

2

Yulei0619@gmail.com

com.google

 省略

 

 

 

3

3

pcsc

com.htc.android.pcsc

 省略

 

 

 

 

contact_id:引用contacts表的_id字段

account_name:指明該聯繫人是從哪一個帳戶上同步下載下來的。

account_type:郵件賬戶的類型

sync1:說明該聯繫人信息是與哪一個網址進行交互(同步聯繫人信息)

 

Table data.

_id

raw_contact_id

mimetype_id

data1

data2

data15

 

1

1

6

南京用友

南京用友

<null>

 

2

1

5

1-318-296-4252

1

<null>

 

3

2

6

司道鵬

司道鵬

<null>

 

4

2

5

1-516-920-8116

2

<null>

 

5

3

6

子江

子江

<null>

 

6

3

5

1795113734017562

1

<null>

 

7

3

5

+8613734017562

2

<null>

 

2366

3

4

<null>

<null>

‰PNG

 

 

raw_contact_id 引用  raw_contacts表的_id字段

mimetype_id

vnd.android.cursor.item/photo -> 4 該行 data15表明照片

vnd.android.cursor.item/phone_v2 -> 5 該行data1表明電話號碼

vnd.android.cursor.item/name -> 6說明該行data1表明聯繫人名稱

 

data15 存儲照片 Type Blob

 

 

Table phone_lookup

_id

data_id

raw_contact_id

normalized_number

1

2

1

25246928131

2

4

2

61180296151

3

6

3

2657104373115971

4

7

3

2657104373168+

 

data_id 引用 data 表的_id字段

raw_contact_id 引用raw_contacts表的 _id字段

normalized_number:聯繫人電話號碼的倒序排列

 

如何將smsmms.db數據庫與contacts2.db數據結合起來成爲了很是關鍵的部分

依據聯繫人的number,查詢該聯繫人的contact_id

public static String getContactId(Context context, String number) {
        Cursor c = null;
        try {
               c = context.getContentResolver().query(Phone.CONTENT_URI,
              new String[] { Phone.CONTACT_ID, Phone.NUMBER }, null,, null);
               if (c != null && c.moveToFirst()) {
                     while (!c.isAfterLast()) {

                         if (PhoneNumberUtils.compare(number, c.getString(1))) {
                                   return c.getString(0);
                         }
                                c.moveToNext();
                        }
                     }
              } catch (Exception e) {
                     Log.e(TAG, "getContactId error:", e);
              } finally {
                     if (c != null) {c.close();
                     }
              }
              return null;
       }

Phone.CONTENT_URI, = content:// com.android.contacts/data/phones

經過查看源代碼發現:該URI主要對應着contacts表,raw_contacts表,data表。這段源碼對於剛瞭解該contact2數據庫的人說比較費勁,

qb.setProjectionMap(distinct ? sDistinctDataProjectionMap : sDataProjectionMap);是很是重要的線索,它告訴咱們會查詢哪些字段

 

在這裏尤爲須要說明的是:PhoneNumberUtils.compare(String a, String b) 方法

加入咱們在手機中把「13466739143」存儲爲聯繫人「張三」(存儲的聯繫手機號僅僅是13466739143),若是張三使用飛信給你發短信(咱們知道用飛信,電話號碼前會自動加12593),用戶確定但願在手機上的未接短信來自張三,而不是有「1259313466739143」被看成一個陌生號碼出現

如何將「1259313466739143」和「13466739143」以及可能的「1795113466739143」,「+8613466739143」等等認爲是同一個電話號碼,這裏的PhoneNumberUtils.compare(String a, String b)就起到了很是關鍵的做用。

  

有了contact_id以後,咱們就能夠作不少事情。

1.依據contact_id,去查詢該聯繫人的照片:

  

public static Bitmap getContactsPhoto(Context context, String contactId) {
        Cursor c = null;
        // load icon
        byte[] icon = null;
           try {
                  // get contact photo URI
                  Uri refUri = Uri.withAppendedPath(Contacts.CONTENT_URI, contactId);
                  refUri = Uri.withAppendedPath(refUri,
                  ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
                  // get cursor
                  c = context.getContentResolver().query(refUri,
                                   new String[] { Photo.PHOTO }, null, null, null);
                 if (c != null && c.moveToFirst()) {
         icon = c.getBlob(0);
                 }
              } catch (Exception e) {
                     e.printStackTrace();
              } finally {
                     if (c != null) {  c.close(); }
                }
              if (icon != null) {
                 try {
                         return BitmapFactory.decodeByteArray(icon, 0, icon.length);
                    } catch (OutOfMemoryError e) {
                            e.printStackTrace();
                            System.gc();
                            return null;
                   }
              }
              return null;
       }

2. 依據該聯繫人的contact_id, 去查詢該聯繫人的名字(好比「張三」)

      

private static String getDisplayName(Context context, String contacts_id) {
              Cursor c = null;
              try {
                    c = context.getContentResolver().query(Contacts.CONTENT_URI,
                                  new String[] { Contacts.DISPLAY_NAME },
                                   Contacts._ID + "=" + contacts_id, null, null);
                     if (c != null && c.moveToFirst()) {
                            return c.getString(0);
                     }
              } catch (Exception e) {
                     e.printStackTrace();
              } finally {
                     if (c != null) {  c.close(); }
              }
              return null;
       }

3. 依據該聯繫人的contact_id ,查詢同一個contact_id有多少個電話號碼。

  (有可能手機上存了張三2個號碼)

public static int getContactNumberCount(Context context, String contactId) {
        int count = 0;
        Cursor c = null;
        try {
             c = context.getContentResolver().query(                                                          ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                       new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER},                                                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID  + " = " + contactId, null, null);                                             
                     if (c != null && c.moveToFirst()) {
                            count = c.getCount();
                    }
              } catch (Exception e) {
                    e.printStackTrace();
              } finally {
                     if (c != null) {  c.close(); }
             }
              return count;
       }

------------------------------------------------------------------------------

若是須要讀取一個聯繫人的信息用CONTENT_LOOKUP_RUI代替CONTENT_URI

若是須要經過電話號碼查找一個聯繫人,用PhoneLookup.CONTENT_FIILTER_URI,這個URI爲這個目的進行了優化;

若是須要經過部分名字的匹配查找,用CONTENT_FILTER_URI;

Android中的通訊錄操做給人的第一感受就是暈,不知道怎麼去用。能夠看下這張我畫的圖:(比較醜,可是地球人都能看懂)

data1.png
這幅圖的意思是: 操做通訊錄本質是去操做合適的ContentProvider,經過合適的ContentProvider去操做通信錄

1: 要想熟練的操做合適的ContentProvider,就必需要掌握一個類ContactsContract(2.0開始使用)。

如今能夠思考一個新問題: Android中的聯繫人信息是如何存儲的?
Android中的聯繫人信息都是存儲在一個叫contacts2.db的數據庫中。數據庫的路徑是:/data/data/com.android.provider.contacts/databases/contacts2.db(在這裏推薦一個sqlite的查看工具: http://www.sqliteexpert.com/  我的版是免費的)

繼續思考一個新問題: 這個數據庫是如何來存儲聯繫人信息的?
根據官方的文檔:通訊錄是一個3層的數據存儲模型(初看挺牛的,說穿了就是3張表)
我又畫了一張圖比較形象的反應這個「3層模型」。
data.png

第一層:Data層,每種獨立的數據類型佔一行。具體哪些獨立的數據能夠佔一行,能夠在mimetypes這張表中找到, 原生Android的系統 一共12種,例如name,phone,email ect..
第二層:RawContracts層,由Data層的多條數據組合成一個完整的聯繫人信息。
第三層:Contracts層,這一層主要注意與第二層的區別。大部分狀況下這兩層的數據時指同一個聯繫人的信息,即他們倆是一一對應的關係,可是有些特殊狀況,這個我是查了一些老外的論壇加上本身的理解,例如 我作一個本地通訊錄和網絡上的通訊錄同步的時候,可能有一我的他在本地存在,他在網絡上也存在,這個時候Android就能夠識別他們,認爲他們兩個實際上是指同一我的。 (這種狀況我沒有試出來,我感受這個實際上是Android創造了這個概念以後,留給咱們開發本身去實現的。)


上面說過「要想熟練的操做合適的ContentProvider,就必需要掌握一個類ContactsContract」,
那麼這個ContactsContract類是幹什麼的呢 
說穿了這個類就是去解釋和翻譯這個contacts2.db數據庫的。 
這個類超大6000+行代碼, 可是確沒有什麼操做代碼,幾乎都是來解釋contacts2.db數據庫的。
這個類中有不少的內部接口和內部類,用來翻譯一些表。 例如Data內部類,RawContacts內部類,等等。

下面我以一個實際的例子講解一下操做過程:
問題: 我有一個電話號碼,如今想去查找這個電話號碼主人的姓名。
第一步:我要肯定用哪個ContentProvider去查詢,這個時候確定想到去用含有電話號碼的ContentProvider去查詢電話號碼所在的Contacts_ID.
第二部:經過Contacts_ID找到對應的Raw_Contacts_ID.
第三部:經過Raw_Contacts_ID找到對應的聯繫人姓名.

肯定了操做步驟以後,確定是去看文檔了,誰也不能猜出來含有電話號碼ContentProvider_URI.

經過查詢文檔: If you need to look up a contact by the phone number, usePhoneLookup.CONTENT_FILTER_URI, which is optimized for this purpose.

咱們肯定了URI以後就能夠開始編寫代碼了。


public String lookupNameByPhoneNumber(Context context,String phoneNumber) {
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
    Cursor cursor = context.getContentResolver().query(uri,
            new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
    String name = null;
    if (cursor == null) {    
    name = null;
    }
    try {
       if (cursor.moveToFirst()) {
           name = cursor.getColumnName(0);
       }
    } finally {
       cursor.close();
    }
    return name;
}

看完這段代碼,你可能想去問,第二步跟第三步跑哪裏去了。這兩步其實已經被封裝在Resoler的query方法中了。若是真要咱們編寫這兩部的話,那咱們還不如不用ContentProvider來的輕鬆。

最後再來講下真機上的通訊錄模型,真機上的通訊錄其實就是原生通訊錄的擴展。增長一些東西。

最重要的一點,原生數據庫裏有的表,表字段,觸發器,視圖或索引,在真機上確定也有。
若是想本身作一個通訊錄,也確定要在原生的通訊錄上擴展,只能增長,不能減小。
想問爲何?
若是你少兩張表的話,可能一些系統功能就崩潰了。

這就不符合google的目的了,我提供了一些功能,你可使用修改,可是你別刪除,因此google火了。

添加一條數據

ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
                .build());        ops.add(ContentProviderOperation 
               .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
                .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, phone)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType).build());
        ops.add(ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) 
               .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType).build());
        // Ask the Contact provider to create a new contact        Log.i(TAG,
                "Selected account: " + mSelectedAccount.getName() + " ("
                        + mSelectedAccount.getType() + ")");
        Log.i(TAG, "Creating contact: " + name);
        try {
            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (Exception e) {
            // Display warning
        }

 

 

---------------------------------------------------------

Android系統中的聯繫人也是經過ContentProvider來對外提供數據的,咱們這裏實現獲取全部聯繫人、經過電話號碼獲取聯繫人、添加聯繫人、使用事務添加聯繫人。

獲取全部聯繫人

1.   Android系統中的聯繫人也是經過ContentProvider來對外提供數據的

2.   數據庫路徑爲:/data/data/com.android.providers.contacts/database/contacts2.db

3.   咱們須要關注的有3張表

     raw_contacts:其中保存了聯繫人id

     data:和raw_contacts是多對一的關係,保存了聯繫人的各項數據

     mimetypes:爲數據類型

4.   Provider的authorites爲com.android.contacts

5.   查詢raw_contacts表的路徑爲:contacts

6.   查詢data表的路徑爲:contacts/#/data

     這個路徑爲鏈接查詢,要查詢「mimetype」字段能夠根據「mimetype_id」查詢到mimetypes表中的數據

7.   先查詢raw_contacts獲得每一個聯繫人的id,在使用id從data表中查詢對應數據,根據mimetype分類數據

示例:

//查詢全部聯繫人  
public void testGetAll() {  
    ContentResolver resolver = getContext().getContentResolver();  
    Uri uri = Uri.parse("content://com.android.contacts/contacts");  
    Cursor idCursor = resolver.query(uri, new String[] { "_id" }, null, null, null);  
    while (idCursor.moveToNext()) {  
        //獲取到raw_contacts表中的id  
        int id = idCursor.getInt(0);   
        //根據獲取到的ID查詢data表中的數據  
        uri = Uri.parse("content://com.android.contacts/contacts/" + id + "/data");  
        Cursor dataCursor = resolver.query(uri, new String[] { "data1", "mimetype" }, null, null, null);  
        StringBuilder sb = new StringBuilder();  
        sb.append("id=" + id);  
        //查詢聯繫人表中的  
        while (dataCursor.moveToNext()) {  
            String data = dataCursor.getString(0);  
            String type = dataCursor.getString(1);  
            if ("vnd.android.cursor.item/name".equals(type))  
                sb.append(", name=" + data);  
            else if ("vnd.android.cursor.item/phone_v2".equals(type))  
                sb.append(", phone=" + data);  
            else if ("vnd.android.cursor.item/email_v2".equals(type))  
                sb.append(", email=" + data);  
        }  
        System.out.println(sb);  
    }  
}


經過電話號碼獲取聯繫人

1.   系統內部提供了根據電話號碼獲取data表數據的功能,路徑爲:data/phones/filter/*

2.   用電話號碼替換「*」部分就能夠查到所需數據,獲取「display_name」能夠獲取到聯繫人顯示名

示例:

//根據電話號碼查詢聯繫人名稱  
public void testGetName() {  
    ContentResolver resolver = getContext().getContentResolver();  
    Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/1111");  
    Cursor c = resolver.query(uri, new String[] { "display_name" }, null, null, null);  
    while (c.moveToNext()) {  
        System.out.println(c.getString(0));  
    }  
}


添加聯繫人

1.   先向raw_contacts表插入id,路徑爲:raw_contacts

2.   獲得id以後再向data表插入數據,路徑爲:data

示例:

//添加聯繫人  
ublic void testInsert() {  
ContentResolver resolver = getContext().getContentResolver();  
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");  
ContentValues values = new ContentValues();  
// 向raw_contacts插入一條除了ID以外, 其餘所有爲NULL的記錄, ID是自動生成的  
long id = ContentUris.parseId(resolver.insert(uri, values));   
//添加聯繫人姓名  
uri = Uri.parse("content://com.android.contacts/data");  
values.put("raw_contact_id", id);  
values.put("data2", "FHM");  
values.put("mimetype", "vnd.android.cursor.item/name");  
resolver.insert(uri, values);  
                //添加聯繫人電話  
values.clear(); // 清空上次的數據  
values.put("raw_contact_id", id);  
values.put("data1", "18600000000");  
values.put("data2", "2");  
values.put("mimetype", "vnd.android.cursor.item/phone_v2");  
resolver.insert(uri, values);  
//添加聯繫人郵箱  
values.clear();  
values.put("raw_contact_id", id);  
values.put("data1", "zxx@itcast.cn");  
values.put("data2", "1");  
values.put("mimetype", "vnd.android.cursor.item/email_v2");  
resolver.insert(uri, values);


使用事務添加聯繫人

1.   在添加聯繫人得時候是分屢次訪問Provider,若是在過程當中出現異常,會出現數據不完整的狀況,這些操做應該放在一次事務中

2.   使用ContentResolver的applyBatch(String authority,ArrayList<ContentProviderOperation> operations) 方法能夠將多個操做在一個事務中執行

3.   文檔位置:

      file:///F:/android-sdk-windows/docs/reference/android/provider/ContactsContract.RawContacts.html

示例:

//使用事務添加聯繫人  
public void testInsertBatch() throws Exception {  
    ContentResolver resolver = getContext().getContentResolver();  
  
    ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();  
  
    ContentProviderOperation operation1 = ContentProviderOperation //  
            .newInsert(Uri.parse("content://com.android.contacts/raw_contacts")) //  
            .withValue("_id", null) //  
            .build();  
    operations.add(operation1);  
  
    ContentProviderOperation operation2 = ContentProviderOperation //  
            .newInsert(Uri.parse("content://com.android.contacts/data")) //  
            .withValueBackReference("raw_contact_id", 0) //  
            .withValue("data2", "ZZH") //  
            .withValue("mimetype", "vnd.android.cursor.item/name") //  
            .build();  
    operations.add(operation2);  
      
    ContentProviderOperation operation3 = ContentProviderOperation //  
            .newInsert(Uri.parse("content://com.android.contacts/data")) //  
            .withValueBackReference("raw_contact_id", 0) //  
            .withValue("data1", "18612312312") //  
            .withValue("data2", "2") //  
            .withValue("mimetype", "vnd.android.cursor.item/phone_v2") //  
            .build();  
    operations.add(operation3);  
  
    ContentProviderOperation operation4 = ContentProviderOperation //  
            .newInsert(Uri.parse("content://com.android.contacts/data")) //  
            .withValueBackReference("raw_contact_id", 0) //  
            .withValue("data1", "zq@itcast.cn") //  
            .withValue("data2", "2") //  
            .withValue("mimetype", "vnd.android.cursor.item/email_v2") //  
            .build();  
    operations.add(operation4);  
  
    // 在事務中對多個操做批量執行  
    resolver.applyBatch("com.android.contacts", operations);  
}
相關文章
相關標籤/搜索