天天看點

Android contacts content provider學習小結

最近想做一個功能,能夠擷取android最近修改的聯系人,包括added/updated/deleted。雖然最後還是沒有找到很好的辦法,但順便學習了下Android contacts content provider機制。

Android contacts content provider是一個系統級别的content provider。content provider提供了一種機制,使得一個應用可以開放出接口供其他應用跨程序調用。而contact content provider則是系統提供的。其調用者(content resolver)可以通過該provider擷取通訊簿資料。

在Android系統中,通訊簿是通過三層表結構來存儲資料的,從上到下分别是Contact,RawContact和Data。

先不管Android自己的實作。如果讓我們設計資料庫來儲存通訊簿,會怎麼做呢?一種做法是隻有一個表,其中包括ID,Name,Phone,Email,Address等項。其問題在于一個使用者可以有多個電話,多個郵件位址。關系型資料庫處理這種一對多的關系的方式就是分成兩個表,第一個表裡隻有ID和Name;第二個表裡每行對應着一個聯系方式,例如第一行是Phone1,第二行Phone2,第三行Email1,...。對于每一行,需要有一個指向第一表的索引。是以第二張表一般包含下面幾列:ID,ContactID(作為外鍵),type(類型,例如是郵件還是電話),data(具體的電話号碼,郵件位址等)。

在Android contact content provider中,RawContact就是第一張表,Data就是第二張表。那為何還有Contact表呢?

我們先做個實驗。現在有一個幹淨的系統,沒有設定帳戶,也沒有添加過聯系人;另外有一個幹淨的gmail帳号,[email protected]。此時我們點選撥号界面裡的新加聯系人,系統會提示“本地儲存”還是“添加帳戶”,這時候如果選擇本地儲存,那該聯系人隻是儲存的機器本地的。假設聯系人為張三。然後我們添加[email protected]為系統帳戶。現在系統會進行同步。如果通過web登入gmail,會發現張三已經在gmail的聯系人中了。如果在web的gmail中添加李四,那李四也會被同步的手機上。如果從系統移除了[email protected],那張三和李四就都會從通訊簿中删除。如果在加上[email protected],那張三和李四就又回來了。

這個實驗說明如果有系統帳号,則本地的聯系人會和服務端的聯系人保持一緻,互相更新。一旦有了系統帳号,所有的聯系人都會有一個源(source)。對于上面的例子,張三和李四的源就是example1@gmail.com。在沒有系統帳号是添加的使用者(張三),其開始時沒有源的,但一旦添加了系統帳号,張三的源就設定為第一個添加的系統帳号。當系統帳号被移除時,表示contacts的源被移除,是以以該帳号為源的contacts都會被移除。

Android系統可以設定多個系統帳号,是以contacts可以支援多個源。假設現在已經添加了[email protected],現在我又添加了example2@gmail.com。現在系統中就有兩個sources。此時再添加contact,則可以選擇是添加到e[email protected]還是[email protected]中。每個源對應的contacts獨立跟伺服器進行同步。

現在問題來了,如果我在[email protected]中也添加了張三。而此張三的電話跟example1@中的電話不同。一種處理方式是被當做完全不同的兩個contact;另外一種方式是合并二者。Android采用了第二種方式。是以如果我此時在web上對example2@添加了張三,那同步之後,張三下面會有兩個電話号碼,分别是example1@和example2@下面添加的。這種歸并從現象上看是通過聯系人名稱來進行的,是以如果重名使用者,可能會發生不錯誤的合并。另外,一個比較詭異的現象是如果對example2@是在手機上添加張三,那聯系人中會有兩個張三;但之後如果删除兩個賬戶,再添加回來,則會進行歸并。估計此歸并操作是在sync的時候做的。

歸并後的聯系人就是Contact表,為支援這種1對多關系,RawContact表中每項又有一個指向Contact表的ID的外鍵。

這三張表都存放在contacts2.db中,隻有系統才能通路。為了讓普通程序也能操作,系統提供了contact content provider開發通路接口,三者對應的URI分别是ContactsContract.Contact.CONTENT_URI,ContactsContract.RawContact.CONTENT_URI和ContactsContract.Data.CONTENT_URI。然後我們就可以通過content resolver通路和操作這些資料了。contact contect provider通過兩個權限READ_CONTACTS和WRITE_CONTACTS來限制通路,需要的應用可以在manifest中設定。

具體的使用可以參考http://developer.android.com/guide/topics/providers/contacts-provider.html。本文前面也很多是參考它寫的。

回到開始的問題,有沒有辦法擷取最近操作的聯系人呢?

我們可以注冊ContentObserver來監聽這Contact表對應的URI的變化。如果手動添加,删除或者修改contact,會觸發;如果在web上修改然後同步下來,也會觸發;如果删除或添加系統帳戶同步下來,同樣會觸發。這都是我們需要的。同時,如果撥打了某個電話,則同樣會觸發,因為餓Contact有相關的列。還有其他可能導緻觸發的情況。

即使這些變化都是我們需要的,那此時我們隻能query到所有的contact。

從API18開始,在Contact表中,新加了CONTACT_LAST_UPDATED_TIMESTAMP列,表示該contact被更新的時間。是以我們可以對其進行排序和過濾。我們可以記錄每次變化觸發的時間,那麼目前時間和上次觸發時間之間的contacts,就是最近變化的。通過這種方式,可以知道最近inserted/updated的contact。

對于deleted contact,也是從API18開始,新加了DeletedContacts表,其中記錄了被删除的contact的日志,有兩列:ID和timestamp。我們仍然可以對timestamp進行排序或過濾,找到此次和上次變化之間的contacts。但問題是這裡隻能拿到ID,而ID此時已經無法查到對應的contact了(因為已經删除了),除非自己儲存下曆史contacts。

是以很遺憾,還是沒有找到合适的辦法。