編輯:關於Android編程
聯系人提供程序是一個強大而又靈活的 Android 組件,用於管理設備上有關聯系人數據的中央存儲庫。 聯系人提供程序是您在設備的聯系人應用中看到的數據源,您也可以在自己的應用中訪問其數據,並可在設備與在線服務之間傳送數據。 提供程序儲存有多種數據源,由於它會試圖為每個聯系人管理盡可能多的數據,因此造成其組織結構非常復雜。 為此,該提供程序的 API 包含豐富的協定類和接口,為數據檢索和修改提供便利。
本指南介紹下列內容:
提供程序基本結構
如何從提供程序檢索數據
如何修改提供程序中的數據
如何編寫用於同步服務器數據與聯系人提供程序數據的同步適配器。
聯系人提供程序組織
聯系人提供程序是 Android 內容提供程序的一個組件。它保留了三種類型的聯系人數據,每一種數據都對應提供程序提供的一個表,如圖 1 所示:
圖 1.聯系人提供程序表結構。
這三個表通常以其協定類的名稱命名。這些類定義表所使用的內容 URI、列名稱及列值相應的常量:
ContactsContract.Contacts表
表示不同聯系人的行,基於聚合的原始聯系人行。
ContactsContract.RawContacts表
包含聯系人數據摘要的行,針對特定用戶帳戶和類型。
ContactsContract.Data表
包含原始聯系人詳細信息(例如電子郵件地址或電話號碼)的行。
由ContactsContract中的協定類表示的其他表是輔助表,聯系人提供程序利用它們來管理其操作,或為設備的聯系人或電話應用中的特定功能提供支持。
原始聯系人
一個原始聯系人表示來自某一帳戶類型和帳戶名稱、有關某個聯系人的數據。 由於聯系人提供程序允許將多個在線服務作為某一聯系人的數據源,因此它允許同一聯系人對應多個原始聯系人。 借助支持多個原始聯系人的特性,用戶還可以將某一聯系人在帳戶類型相同的多個帳戶中的數據進行合並。
原始聯系人的大部分數據並不存儲在ContactsContract.RawContacts表內,而是存儲在ContactsContract.Data表中的一行或多行內。每個數據行都有一個Data.RAW_CONTACT_ID列,其中包含其父級ContactsContract.RawContacts行的android.provider.BaseColumns#_ID RawContacts._ID值。
重要的原始聯系人列
表 1 列出了ContactsContract.RawContacts表中的重要列。 請閱讀表後的說明:
表 1.重要的原始聯系人列。
列名稱用途備注
ACCOUNT_NAME作為該原始聯系人來源的帳戶類型的帳戶名稱。 例如,Google 帳戶的帳戶名稱是設備所有者的某個 Gmail 地址。此名稱的格式專用於其帳戶類型。它不一定是電子郵件地址。
ACCOUNT_TYPE作為該原始聯系人來源的帳戶類型。例如,Google 帳戶的帳戶類型是com.google。 請務必使用您擁有或控制的域的域標識符限定您的帳戶類型。 這可以確保您的帳戶類型具有唯一性。提供聯系人數據的帳戶類型通常關聯有同步適配器,用於與聯系人提供程序進行同步。
DELETED原始聯系人的“已刪除”標志。此標志讓聯系人提供程序能夠在內部保留該行,直至同步適配器能夠從服務器刪除該行,然後再從存儲庫中最終刪除該行。
說明
以下是關於ContactsContract.RawContacts表的重要說明:
原始聯系人的姓名並不存儲其在ContactsContract.RawContacts中的行內,而是存儲在ContactsContract.Data表的ContactsContract.CommonDataKinds.StructuredName行內。一個原始聯系人在ContactsContract.Data表中只有一個該類型的行。
注意:要想在原始聯系人行中使用您自己的帳戶數據,必須先在AccountManager中注冊帳戶。 為此,請提示用戶將帳戶類型及其帳戶名稱添加到帳戶列表。 如果您不這樣做,聯系人提供程序將自動刪除您的原始聯系人行。
例如,如果您想讓您的應用為您域名為com.example.dataservice、基於 Web 的服務保留聯系人數據,並且您的服務的用戶帳戶是[email protected],則用戶必須先添加帳戶“類型”(com.example.dataservice) 和帳戶“名稱”([email protected]),然後您的應用才能添加原始聯系人行。 您可以在文檔中向用戶解釋這項要求,也可以提示用戶添加類型和名稱,或者同時采用這兩種措施。 下文對帳戶類型和帳戶名稱做了更詳盡的描述。
原始聯系人數據來源
為理解原始聯系人的工作方式,假設有一位用戶“Emily Dickinson”,她的設備上定義了以下三個用戶帳戶:
Twitter 帳戶“belle_of_amherst”
該用戶已在Accounts設置中為全部三個帳戶啟用了Sync Contacts。
假定 Emily Dickinson 打開一個浏覽器窗口,以[email protected]身份登錄 Gmail,然後打開 “聯系人”,並添加“Thomas Higginson”。後來,她以[email protected]身份登錄 Gmail,並向“Thomas Higginson”發送一封電子郵件,此操作會自動將他添加為聯系人。 她還在 Twitter 上關注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。
以上操作的結果是,聯系人提供程序會創建以下這三個原始聯系人:
第一個原始聯系人對應“Thomas Higginson”,關聯帳戶[email protected]。 用戶帳戶類型是 Google。
第二個原始聯系人對應“Thomas Higginson”,關聯帳戶[email protected]。 用戶帳戶類型也是 Google。由於添加的聯系人對應的用戶帳戶不同,因此盡管名稱與前一名稱完全相同,也只能作為第二個原始聯系人。
第三個原始聯系人對應“Thomas Higginson”,關聯帳戶“belle_of_amherst”。用戶帳戶類型是 Twitter。
數據
如前文所做的說明,原始聯系人的數據存儲在一個ContactsContract.Data行中,該行鏈接到原始聯系人的_ID值。這使一位原始聯系人可以擁有多個具有相同數據類型的實例,例如電子郵件地址或電話號碼。 例如,如果對應[email protected]的“Thomas Higginson”(關聯 Google 帳戶[email protected]的 Thomas Higginson 的原始聯系人行)的住宅電子郵件地址為[email protected],辦公電子郵件地址為[email protected],則聯系人提供程序會存儲這兩個電子郵件地址行,並將它們都鏈接到原始聯系人。
請注意,這個表中存儲了不同類型的數據。顯示姓名、電話號碼、電子郵件、郵政地址、照片以及網站明細行都可以在ContactsContract.Data表中找到。 為便於管理這些數據,ContactsContract.Data表為一些列使用了描述性名稱,為其他列使用了通用名稱。 使用描述性名稱的列的內容具有相同的含義,與行中數據的類型無關,而使用通用名稱的列的內容則會隨數據類型的不同而具有不同的含義。
描述性列名稱
以下是一些描述性列名稱的示例:
RAW_CONTACT_ID
該數據對應的原始聯系人_ID列的值。
MIMETYPE
該行中存儲的數據類型,以自定義 MIME(多用途互聯網郵件擴展)類型表示。聯系人提供程序使用了ContactsContract.CommonDataKinds子類中定義的 MIME 類型。 這些 MIME 類型為開源類型,可供與聯系人提供程序協作的任何應用或同步適配器使用。
IS_PRIMARY
如果一個原始聯系人可能具有多個這種類型的數據行,IS_PRIMARY列會標記 包含該類型主要數據的數據行。例如,如果用戶長按某個聯系人的電話號碼,並選擇Set default,則包含該號碼的ContactsContract.Data行會將其IS_PRIMARY列設置為一個非零值。
通用列名稱
有 15 個通用列命名為DATA1至DATA15,可普遍適用;還有四個通用列命名為SYNC1至SYNC4,只應由同步適配器使用。 通用列名稱常量始終有效,與行包含的數據類型無關。
DATA1列為索引列。聯系人提供程序總是在此列中存儲其預期會成為最頻繁查詢目標的數據。 例如,在一個電子郵件行中,此列包含實際電子郵件地址。
按照慣例,DATA15為預留列,用於存儲照片縮略圖等二進制大型對象 (BLOB) 數據。
類型專用列名稱
為便於處理特定類型行的列,聯系人提供程序還提供了ContactsContract.CommonDataKinds子類中定義的類型專用列名稱常量。 這些常量只是為同一列名稱提供不同的常量名稱,這有助於您訪問特定類型行中的數據。
例如,ContactsContract.CommonDataKinds.Email類為ContactsContract.Data行定義類型專用列名稱常量,該行的 MIME 類型為Email.CONTENT_ITEM_TYPE。 該類包含電子郵件地址列的ADDRESS常量。ADDRESS的實際值為“data1”,這與列的通用名稱相同。
注意:請勿使用具有提供程序某個預定義 MIME 類型的行向ContactsContract.Data表中添加您自己的自定義數據。 否則您可能會丟失數據,或導致提供程序發生故障。 例如,如果某一行具有 MIME 類型Email.CONTENT_ITEM_TYPE,並且DATA1列包含的是用戶名而不是電子郵件地址,您就不應添加該行。如果您為該行使用自定義的 MIME 類型,則可自由定義您的自定義類型專用的列名稱,並隨心所欲地使用這些列。
圖 2 顯示的是描述性列和數據列在ContactsContract.Data行中的顯示情況,以及類型專用列名稱“覆蓋”通用列名稱的情況
圖 2.類型專用列名稱和通用列名稱。
類型專用列名稱類
表 2 列出了最常用的類型專用列名稱類:
表 2.類型專用列名稱類
映射類數據類型備注
ContactsContract.CommonDataKinds.StructuredName與該數據行關聯的原始聯系人的姓名數據。一位原始聯系人只有其中一行。
ContactsContract.CommonDataKinds.Photo與該數據行關聯的原始聯系人的主要照片。一位原始聯系人只有其中一行。
ContactsContract.CommonDataKinds.Email與該數據行關聯的原始聯系人的電子郵件地址。一位原始聯系人可有多個電子郵件地址。
ContactsContract.CommonDataKinds.StructuredPostal與該數據行關聯的原始聯系人的郵政地址。一位原始聯系人可有多個郵政地址。
ContactsContract.CommonDataKinds.GroupMembership將原始聯系人鏈接到聯系人提供程序內其中一組的標識符。組是帳戶類型和帳戶名稱的一項可選功能。
聯系人
聯系人提供程序通過將所有帳戶類型和帳戶名稱的原始聯系人行合並來形成聯系人。 這可以為顯示和修改用戶針對某一聯系人收集的所有數據提供便利。 聯系人提供程序管理新聯系人行的創建,以及原始聯系人與現有聯系人行的合並。 系統不允許應用或同步適配器添加聯系人,並且聯系人行中的某些列是只讀列。
注:如果您試圖通過insert()向聯系人提供程序添加聯系人,會引發一個UnsupportedOperationException異常。 如果您試圖更新一個列為“只讀”的列,更新會被忽略。
如果添加的新原始聯系人不匹配任何現有聯系人,聯系人提供程序會相應地創建新聯系人。 如果某個現有原始聯系人的數據發生了變化,不再匹配其之前關聯的聯系人,則提供程序也會執行此操作。 如果應用或同步適配器創建的新原始聯系人“的確”匹配某位現有聯系人,則新原始聯系人將與現有聯系人合並。
聯系人提供程序通過Contacts表中聯系人行的_ID列將聯系人行與其各原始聯系人行鏈接起來。 原始聯系人表ContactsContract.RawContacts的CONTACT_ID列包含對應於每個原始聯系人行所關聯聯系人行的_ID值。
ContactsContract.Contacts表還有一個android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY列,它是一個指向聯系人行的“永久性”鏈接。 由於聯系人提供程序會自動維護聯系人,因此可能會在合並或同步時相應地更改聯系人行的android.provider.BaseColumns#_ID值。 即使發生這種情況,合並了聯系人android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY的內容 URICONTENT_LOOKUP_URI仍將指向聯系人行,這樣,您就能使用android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY保持指向“最喜愛”聯系人的鏈接,以及執行其他操作。 該列具有其自己的格式,與android.provider.BaseColumns#_ID列的格式無關。
圖 3 顯示的是這三個主要表的相互關系。
圖 3.聯系人表、原始聯系人表與詳細信息表之間的關系。
來自同步適配器的數據
雖然用戶是直接將聯系人數據輸入到設備中,但這些數據也會通過同步適配器從 Web 服務流入聯系人提供程序中,這些同步適配器可自動化設備與服務之間的數據傳送。 同步適配器在系統控制下在後台運行,它們會調用ContentResolver方法來管理數據。
在 Android 中,與同步適配器協作的 Web 服務通過帳戶類型加以標識。 每個同步適配器都與一個帳戶類型協作,但它可以支持該類型的多個帳戶名稱。下列定義提供了更多詳細信息,並描述了帳戶類型及帳戶名稱與同步適配器及服務之間的關系。
帳戶類型
表示用戶在其中存儲數據的服務。在大多數時候,用戶需要向服務驗證身份。 例如,Google Contacts 是一個以代碼google.com標識的帳戶類型。 該值對應於AccountManager使用的帳戶類型。
帳戶名稱
表示某個帳戶類型的特定帳戶或登錄名。Google Contacts 帳戶與 Google 帳戶相同,都是以電子郵件地址作為帳戶名稱。 其他服務可能使用一個單詞的用戶名或數字 ID。
帳戶類型不必具有唯一性。用戶可以配置多個 Google Contacts 帳戶並將它們的數據下載到聯系人提供程序;如果用戶為個人帳戶名稱和工作帳戶名稱分別設置了一組聯系人,就可能發生這種情況。 帳戶名稱通常具有唯一性。 它們共同標識聯系人提供程序與外部服務之間的特定數據流。
如果您想將服務的數據傳送到聯系人提供程序,則需編寫您自己的同步適配器。
圖 4 顯示的是聯系人提供程序如何融入聯系人數據的流動。 在名為“同步適配器”的方框中,每個適配器都以其帳戶類型命名。
圖 4.聯系人提供程序數據流。
所需權限
想要訪問聯系人提供程序的應用必須請求以下權限:
對一個或多個表的讀取權限
READ_CONTACTS,在AndroidManifest.xml中指定,使用
對一個或多個表的寫入權限
WRITE_CONTACTS,在AndroidManifest.xml中指定,使用
這些權限不適用於用戶個人資料數據。下面的用戶個人資料部分對用戶個人資料及其所需權限做了闡述。
請切記,用戶的聯系人數據屬於個人敏感數據。用戶關心其隱私權,因此不希望應用收集有關其自身的數據或其聯系人的數據。 如需權限來訪問其聯系人數據的理由並不充分,用戶可能給您的應用作出差評或干脆拒絕安裝。
用戶個人資料
ContactsContract.Contacts表有一行包含設備用戶的個人資料數據。 這些數據描述設備的user而不是用戶的其中一位聯系人。 對於每個使用個人資料的系統,該個人資料聯系人行都鏈接到某個原始聯系人行。 每個個人資料原始聯系人行可具有多個數據行。ContactsContract.Profile類中提供了用於訪問用戶個人資料的常量。
訪問用戶個人資料需要特殊權限。除了進行讀取和寫入所需的READ_CONTACTS和WRITE_CONTACTS權限外,如果想訪問用戶個人資料,還分別需要android.Manifest.permission#READ_PROFILE和android.Manifest.permission#WRITE_PROFILE權限進行讀取和寫入訪問。
請切記,您應該將用戶的個人資料視為敏感數據。android.Manifest.permission#READ_PROFILE權限讓您可以訪問設備用戶的個人身份識別數據。 請務必在您的應用的描述中告知用戶您需要用戶個人資料訪問權限的原因。
要檢索包含用戶個人資料的聯系人行,請調用ContentResolver.query()。 將內容 URI 設置為CONTENT_URI並且不要提供任何選擇條件。 您還可以使用該內容 URI 作為檢索原始聯系人或個人資料數據的基本 URI。 例如,以下代碼段用於檢索個人資料數據:
// Sets the columns to retrieve for the user profile
mProjection = new String[]
{
Profile._ID,
Profile.DISPLAY_NAME_PRIMARY,
Profile.LOOKUP_KEY,
Profile.PHOTO_THUMBNAIL_URI
};
// Retrieves the profile from the Contacts Provider
mProfileCursor =
getContentResolver().query(
Profile.CONTENT_URI,
mProjection ,
null,
null,
null);
注:如果您要檢索多個聯系人行並想要確定其中一個是否為用戶個人資料,請測試該行的IS_USER_PROFILE列。 如果該聯系人是用戶個人資料,則此列設置為“1”。
聯系人提供程序元數據
聯系人提供程序管理用於追蹤存儲庫中聯系人數據狀態的數據。 這些有關存儲庫的元數據存儲在各處,其中包括原始聯系人表行、數據表行和聯系人表行、ContactsContract.Settings表以及ContactsContract.SyncState表。 下表顯示的是每一部分元數據的作用:
表 3.聯系人提供程序中的元數據
表列值含義
ContactsContract.RawContactsDIRTY“0”:上次同步以來未發生變化。標記設備上因發生變化而需要同步回服務器的原始聯系人。 當 Android 應用更新行時,聯系人提供程序會自動設置該值。
修改原始聯系人表或數據表的同步適配器應始終向他們使用的內容 URI 追加字符串CALLER_IS_SYNCADAPTER。 這可以防止提供程序將行標記為已更新。 否則,即使服務器是修改的來源,同步適配器修改仍顯示為本地修改,並會發送到服務器。
“1”:上次同步以來發生了變化,需要同步回服務器。
ContactsContract.RawContactsVERSION此行的版本號。每當行或其相關數據發生變化時,聯系人提供程序都會自動增加此值。
ContactsContract.DataDATA_VERSION此行的版本號。每當數據行發生變化時,聯系人提供程序都會自動增加此值。
ContactsContract.RawContactsSOURCE_ID一個字符串值,用於在創建此原始聯系人的帳戶中對該聯系人進行唯一標識。當同步適配器創建新原始聯系人時,此列應設置為該原始聯系人在服務器中的唯一 ID。 當 Android 應用創建新原始聯系人時,應將此列留空。 這是為了向同步適配器表明,它應該在服務器上創建新原始聯系人,並獲取SOURCE_ID的值。
具體地講,對於每個帳戶類型,該源 ID 都必須是唯一的,並且應在所有同步中保持穩定:
唯一:帳戶的每個原始聯系人都必須有自己的源 ID。如果您不強制執行此要求,會在聯系人應用中引發問題。 請注意,帳戶類型相同的兩個原始聯系人可以具有相同的源 ID。 例如,允許帳戶[email protected]的原始聯系人“Thomas Higginson”與帳戶[email protected]的原始聯系人“Thomas Higginson”具有相同的源 ID。
穩定:源 ID 是該原始聯系人在在線服務中的數據的永久性組成部分。 例如,如果用戶從應用設置中清除存儲的聯系人數據並重新同步,則恢復的原始聯系人的源 ID 應與以前相同。 如果您不強制執行此要求,快捷方式將停止工作。
ContactsContract.GroupsGROUP_VISIBLE“0”:此組中的聯系人在 Android 應用 UI 中不應處於可見狀態。此列用於兼容那些允許用戶隱藏特定組中聯系人的服務器。
“1”:系統允許此組中的聯系人在應用 UI 中處於可見狀態。
ContactsContract.SettingsUNGROUPED_VISIBLE“0”:對於此帳戶和帳戶類型,未歸入組的聯系人在 Android 應用 UI 中處於不可見狀態。默認情況下,如果聯系人的所有原始聯系人都未歸入組,則它們將處於不可見狀態(原始聯系人的組成員身份通過ContactsContract.Data表中的一個或多個ContactsContract.CommonDataKinds.GroupMembership行指示)。 通過在ContactsContract.Settings表行中為帳戶類型和帳戶設置此標志,您可以強制未歸入組的聯系人處於可見狀態。 此標志的一個用途是顯示不使用組的服務器上的聯系人。
“1”:對於此帳戶和帳戶類型,未歸入組的聯系人在應用 UI 中處於可見狀態。
ContactsContract.SyncState(所有列)此表用於存儲同步適配器的元數據。利用此表,您可以將同步狀態及其他同步相關數據持久地存儲在設備中。
聯系人提供程序訪問
本節描述訪問聯系人提供程序中數據的准則,側重於闡述以下內容:
實體查詢。
批量修改。
通過 Intent 執行檢索和修改。
數據完整性。
查詢實體
由於聯系人提供程序表是以層級形式組織,因此對於檢索某一行以及與其鏈接的所有“子”行,往往很有幫助。 例如,要想顯示某位聯系人的所有信息,您可能需要檢索某個ContactsContract.Contacts行的所有ContactsContract.RawContacts行,或者檢索某個ContactsContract.RawContacts行的所有ContactsContract.CommonDataKinds.Email行。 為便於執行此操作,聯系人提供程序提供了實體構造,其作用類似於表間的數據庫連接。
實體類似於一個表,由父表及其子表中的選定列組成。 當您查詢實體時,需要根據實體中的可用列提供投影和搜索條件。 結果會得到一個Cursor,檢索的每個子表行在其中都有一行與之對應。 例如,如果您在ContactsContract.Contacts.Entity中查詢某個聯系人姓名以及該姓名所有原始聯系人的所有ContactsContract.CommonDataKinds.Email行,您會獲得一個Cursor,每個ContactsContract.CommonDataKinds.Email行在其中都有一行與之對應。
實體簡化了查詢。使用實體時,您可以一次性檢索聯系人或原始聯系人的所有聯系人數據,而不必先通過查詢父表獲得ID,然後通過該 ID 查詢子表。此外,聯系人提供程序可通過單一事務處理實體查詢,這確保了所檢索數據的內部一致性。
注:實體通常不包含父表和子表的所有列。 如果您試圖使用的列名稱並未出現在實體的列名稱常量列表中,則會引發一個Exception。
以下代碼段說明如何檢索某位聯系人的所有原始聯系人行。該代碼段是一個大型應用的組成部分,包含“主”和“詳”兩個 Activity。 主 Activity 顯示一個聯系人行列表;當用戶選擇一行時,該 Activity 會將其 ID 發送至詳 Activity。 詳 Activity 使用ContactsContract.Contacts.Entity顯示與所選聯系人關聯的所有原始聯系人中的所有數據行。
以下代碼段摘自“detail”Activity:
...
/*
* Appends the entity path to the URI. In the case of the Contacts Provider, the
* expected URI is content://com.google.contacts/#/entity (# is the ID value).
*/
mContactUri = Uri.withAppendedPath(
mContactUri,
ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
// Initializes the loader identified by LOADER_ID.
getLoaderManager().initLoader(
LOADER_ID, // The identifier of the loader to initialize
null, // Arguments for the loader (in this case, none)
this); // The context of the activity
// Creates a new cursor adapter to attach to the list view
mCursorAdapter = new SimpleCursorAdapter(
this, // the context of the activity
R.layout.detail_list_item, // the view item containing the detail widgets
mCursor, // the backing cursor
mFromColumns, // the columns in the cursor that provide the data
mToViews, // the views in the view item that display the data
0); // flags
// Sets the ListView's backing adapter.
mRawContactList.setAdapter(mCursorAdapter);
...
@Override
public Loader onCreateLoader(int id, Bundle args) {
/*
* Sets the columns to retrieve.
* RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
* DATA1 contains the first column in the data row (usually the most important one).
* MIMETYPE indicates the type of data in the data row.
*/
String[] projection =
{
ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
ContactsContract.Contacts.Entity.DATA1,
ContactsContract.Contacts.Entity.MIMETYPE
};
/*
* Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
* contact collated together.
*/
String sortOrder =
ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
" ASC";
/*
* Returns a new CursorLoader. The arguments are similar to
* ContentResolver.query(), except for the Context argument, which supplies the location of
* the ContentResolver to use.
*/
return new CursorLoader(
getApplicationContext(), // The activity's context
mContactUri, // The entity content URI for a single contact
projection, // The columns to retrieve
null, // Retrieve all the raw contacts and their data rows.
null, //
sortOrder); // Sort by the raw contact ID.
}
加載完成時,LoaderManager會調用一個onLoadFinished()回調。此方法的傳入參數之一是一個Cursor,其中包含查詢的結果。在您自己的應用中,您可以從該Cursor獲取數據,以進行顯示或做進一步處理。
批量修改
您應盡可能地通過創建一個ContentProviderOperation對象ArrayList並調用applyBatch(),以“批處理模式”在聯系人提供程序中插入、更新和刪除數據。 由於聯系人提供程序是在applyBatch()中通過單一事務執行所有操作,因此您的修改絕不會使聯系人存儲庫出現不一致問題。 此外,批量修改還有便於同時插入原始聯系人及其明細數據。
注:要修改單個原始聯系人,可以考慮向設備的聯系人應用發送一個 Intent,而不是在您的應用中處理修改。
屈服點
一個包含大量操作的批量修改可能會阻斷其他進程,導致糟糕的總體用戶體驗。 要將您想執行的所有修改組織到盡可能少的單獨列表中,同時防止它們阻斷系統,則應為一項或多項操作設置屈服點。 屈服點是一個ContentProviderOperation對象,其isYieldAllowed()值設置為true。當聯系人提供程序遇到屈服點時,它會暫停其工作,讓其他進程運行,並關閉當前事務。 當提供程序再次啟動時,它會繼續執行ArrayList中的下一項操作,並啟動一個新的事務。
屈服點會導致每次調用applyBatch()會產生多個事務。因此,您應該為針對一組相關行的最後一項操作設置屈服點。 例如,您應該為一組操作中添加原始聯系人行及其關聯數據行的最後一項操作,或者針對一組與一位聯系人相關的行的最後一項操作設置屈服點。
屈服點也是一個原子操作單元。兩個屈服點之間所有訪問的成功或失敗都將以一個單元的形式出現。 如果您不設置任何屈服點,則最小的原子操作是整個批量操作。 如果您使用了屈服點,則可以防止操作降低系統性能,還可確保一部分操作是原子操作。
修改向後引用
當您將一個新原始聯系人行及其關聯的數據行作為一組ContentProviderOperation對象插入時,需要通過將原始聯系人的android.provider.BaseColumns#_ID值作為RAW_CONTACT_ID值插入,將數據行鏈接到原始聯系人行。 不過,當您為數據行創建ContentProviderOperation時,該值不可用,因為您尚未對原始聯系人行應用ContentProviderOperation。 為解決此問題,ContentProviderOperation.Builder類使用了withValueBackReference()方法。 該方法讓您可以插入或修改包含上一操作結果的列。
withValueBackReference()方法具有兩個參數:
key
鍵-值對的鍵。此參數的值應為您要修改的表中某一列的名稱。
previousResult
applyBatch()中ContentProviderResult對象數組內某一值以 0 開始的索引。 應用批處理操作時,每個操作的結果都存儲在一個中間結果數組內。previousResult值是其中一個結果的索引,它通過key值進行檢索和存儲。 這樣,您就可以插入一條新的原始聯系人記錄,並取回其android.provider.BaseColumns#_ID值,然後在添加ContactsContract.Data行時“向後引用”該值。
系統會在您首次調用applyBatch()時創建整個結果數組,其大小與您提供的ContentProviderOperation對象的ArrayList大小相等。 不過,結果數組中的所有元素都設置為null,如果您試圖向後引用某個尚未應用的操作的結果,withValueBackReference()會引發一個Exception。
以下代碼段說明如何批量插入新原始聯系人和數據。代碼段中包括用於建立屈服點和使用向後引用的代碼。 這些代碼段是擴展版本的createContacEntry()方法,該方法是ContactManager示例應用中ContactAdder類的組成部分。
第一個代碼段用於檢索 UI 中的聯系人數據。此時,用戶已經選擇了應添加新原始聯系人的帳戶。
// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
/*
* Gets values from the UI
*/
String name = mContactNameEditText.getText().toString();
String phone = mContactPhoneEditText.getText().toString();
String email = mContactEmailEditText.getText().toString();
int phoneType = mContactPhoneTypes.get(
mContactPhoneTypeSpinner.getSelectedItemPosition());
int emailType = mContactEmailTypes.get(
mContactEmailTypeSpinner.getSelectedItemPosition());
下一個代碼段用於創建將該原始聯系人行插入ContactsContract.RawContacts表的操作:
/*
* Prepares the batch operation for inserting a new raw contact and its data. Even if
* the Contacts Provider does not have any data for this person, you can't add a Contact,
* only a raw contact. The Contacts Provider will then add a Contact automatically.
*/
// Creates a new array of ContentProviderOperation objects.
ArrayList ops =
new ArrayList();
/*
* Creates a new raw contact with its account type (server type) and account name
* (user's account). Remember that the display name is not stored in this row, but in a
* StructuredName data row. No other data is required.
*/
ContentProviderOperation.Builder op =
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
// Builds the operation and adds it to the array of operations
ops.add(op.build());
接著,代碼會創建顯示姓名行、電話行和電子郵件行的數據行。
每個操作生成器對象都使用withValueBackReference()來獲取RAW_CONTACT_ID。引用指回來自第一次操作的ContentProviderResult對象,第一次操作就是添加原始聯系人行並返回其新android.provider.BaseColumns#_ID值。 結果是,每個數據行都通過其RAW_CONTACT_ID自動鏈接到其所屬的ContactsContract.RawContacts行。
添加電子郵件行的ContentProviderOperation.Builder對象帶有withYieldAllowed()標志,用於設置屈服點:
// Creates the display name for the new raw contact, as a StructuredName data row.
op =
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
/*
* withValueBackReference sets the value of the first argument to the value of
* the ContentProviderResult indexed by the second argument. In this particular
* call, the raw contact ID column of the StructuredName data row is set to the
* value of the result returned by the first operation, which is the one that
* actually adds the raw contact row.
*/
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
// Sets the data row's MIME type to StructuredName
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
// Sets the data row's display name to the name in the UI.
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
// Builds the operation and adds it to the array of operations
ops.add(op.build());
// Inserts the specified phone number and type as a Phone data row
op =
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
/*
* Sets the value of the raw contact id column to the new raw contact ID returned
* by the first operation in the batch.
*/
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
// Sets the data row's MIME type to Phone
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
// Sets the phone number and type
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
// Builds the operation and adds it to the array of operations
ops.add(op.build());
// Inserts the specified email and type as a Phone data row
op =
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
/*
* Sets the value of the raw contact id column to the new raw contact ID returned
* by the first operation in the batch.
*/
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
// Sets the data row's MIME type to Email
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
// Sets the email address and type
.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
/*
* Demonstrates a yield point. At the end of this insert, the batch operation's thread
* will yield priority to other threads. Use after every set of operations that affect a
* single contact, to avoid degrading performance.
*/
op.withYieldAllowed(true);
// Builds the operation and adds it to the array of operations
ops.add(op.build());
最後一個代碼段顯示的是applyBatch()調用,用於插入新原始聯系人行和數據行。
// Ask the Contacts Provider to create a new contact
Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
mSelectedAccount.getType() + ")");
Log.d(TAG,"Creating contact: " + name);
/*
* Applies the array of ContentProviderOperation objects in batch. The results are
* discarded.
*/
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
// Display a warning
Context ctx = getApplicationContext();
CharSequence txt = getString(R.string.contactCreationFailure);
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(ctx, txt, duration);
toast.show();
// Log exception
Log.e(TAG, "Exception encountered while inserting contact: " + e);
}
}
此外,您還可以利用批處理操作實現樂觀並發控制,這是一種無需鎖定底層存儲庫便可應用修改事務的控制方法。 要使用此方法,您需要應用事務,然後檢查是否存在可能已同時做出的其他修改。 如果您發現了不一致的修改,請回滾事務並重試。
樂觀並發控制對於移動設備很有用,因為在移動設備上,同一時間只有一位用戶,並且同時訪問數據存儲庫的情況很少見。 由於未使用鎖定功能,因此不用浪費時間設置鎖定或等待其他事務解除鎖定。
要在更新某個ContactsContract.RawContacts行時使用樂觀並發控制,請按以下步驟操作:
檢索原始聯系人的VERSION列以及要檢索的其他數據。
創建一個適合使用newAssertQuery(Uri)方法強制執行約束 的ContentProviderOperation.Builder對象。對於內容 URI,請使用追加有原始聯系人android.provider.BaseColumns#_ID的RawContacts.CONTENT_URI。
對於ContentProviderOperation.Builder對象,請調用withValue(),對VERSION列與您剛檢索的版本號進行比較。
對於同一ContentProviderOperation.Builder,請調用withExpectedCount(),確保此斷言只對一行進行測試。
調用build()創建ContentProviderOperation對象,然後將此對象添加為要傳遞至applyBatch()的ArrayList中的第一個對象。
應用批處理事務。
如果在您讀取原始聯系人行到您試圖對其進行修改這段時間有另一項操作更新了該行,“斷言”ContentProviderOperation將會失敗,系統將終止整個批處理操作。 此情況下,您可以選擇重新執行批處理操作,或執行其他某操作。
以下代碼段演示如何在使用CursorLoader查詢一位原始聯系人後創建一個“斷言”ContentProviderOperation:
/*
* The application uses CursorLoader to query the raw contacts table. The system calls this method
* when the load is finished.
*/
public void onLoadFinished(Loader loader, Cursor cursor) {
// Gets the raw contact's _ID and VERSION values
mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}
...
// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);
// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList;
ops.add(assertOp.build());
// You would add the rest of your batch operations to "ops" here
...
// Applies the batch. If the assert fails, an Exception is thrown
try
{
ContentProviderResult[] results =
getContentResolver().applyBatch(AUTHORITY, ops);
} catch (OperationApplicationException e) {
// Actions you want to take if the assert operation fails go here
}
通過 Intent 執行檢索和修改
通過向設備的聯系人應用發送 Intent,您可以間接訪問聯系人提供程序。 Intent 會啟動設備的聯系人應用 UI,用戶可以在其中執行與聯系人有關的操作。 通過這種訪問方式,用戶可以:
從列表中選取一位聯系人並將其返回給您的應用以執行進一步操作。
編輯現有聯系人的數據。
為其任一帳戶插入新原始聯系人。
刪除聯系人或聯系人數據。
如果用戶要插入或更新數據,您可以先收集數據,然後將其作為 Intent 的一部分發送。
當您使用 Intent 通過設備的聯系人應用訪問聯系人提供程序時,您無需自行編寫用於訪問該提供程序的 UI 或代碼。 您也無需請求對提供程序的讀取或寫入權限。 設備的聯系人應用可以將聯系人讀取權限授予給您,而且您是通過另一個應用對該提供程序進行修改,不需要擁有寫入權限。
表 4 匯總了您為可用任務使用的操作、MIME 類型以及數據值,ContactsContract.Intents.Insert參考文檔列出了您可用於putExtra()的 Extra 值:
表 4.聯系人提供程序 Intent。
任務操作數據MIME 類型備注
從列表中選取一位聯系人ACTION_PICK下列值之一:
Contacts.CONTENT_URI,顯示聯系人列表。
Phone.CONTENT_URI,顯示原始聯系人的電話號碼列表。
StructuredPostal.CONTENT_URI,顯示原始聯系人的郵政地址列表。
Email.CONTENT_URI,顯示原始聯系人的電子郵件地址列表。
未使用顯示原始聯系人列表或一位原始聯系人的數據列表,具體取決於您提供的內容 URI 類型。
調用startActivityForResult()方法,該方法返回所選行的內容 URI。 該 URI 的形式為:追加有該行LOOKUP_ID的表的內容 URI。 設備的聯系人應用會在 Activity 的生命周期內將讀取和寫入權限授予給此內容 URI。
插入新原始聯系人Insert.ACTION不適用RawContacts.CONTENT_TYPE,用於一組原始聯系人的 MIME 類型。顯示設備聯系人應用的添加聯系人屏幕。系統會顯示您添加到 Intent 中的 Extra 值。 如果是隨startActivityForResult()發送,系統會將新添加的原始聯系人的內容 URI 傳回給onActivityResult()回調方法並作為後者Intent參數的“data”字段。 要獲取該值,請調用getData()。
編輯聯系人ACTION_EDIT該聯系人的CONTENT_LOOKUP_URI。 該編輯器 Activity 讓用戶能夠對任何與該聯系人關聯的數據進行編輯。Contacts.CONTENT_ITEM_TYPE,一位聯系人。顯示聯系人應用中的“編輯聯系人”屏幕。系統會顯示您添加到 Intent 中的 Extra 值。 當用戶點擊完成保存編輯時,您的 Activity 會返回前台。
顯示一個同樣可以添加數據的選取器。ACTION_INSERT_OR_EDIT不適用CONTENT_ITEM_TYPE此 Intent 始終顯示聯系人應用的選取器屏幕。用戶可以選取要編輯的聯系人,或添加新聯系人。 根據用戶的選擇,系統會顯示編輯屏幕或添加屏幕,還會顯示您使用 Intent 傳遞的 Extra 數據。 如果您的應用顯示電子郵件或電話號碼等聯系人數據,請使用此 Intent 來允許用戶向現有聯系人添加數據。
注:不需要通過此 Intent 的 Extra 發送姓名值,因為用戶總是會選取現有姓名或添加新姓名。 此外,如果您發送姓名,並且用戶選擇執行編輯操作,則聯系人應用將顯示您發送的姓名,該姓名將覆蓋以前的值。 如果用戶未注意這一情況便保存了編輯,原有值將會丟失。
設備的聯系人應用不允許您使用 Intent 刪除原始聯系人或其任何數據。 因此,要刪除原始聯系人,請使用ContentResolver.delete()或ContentProviderOperation.newDelete()。
以下代碼段說明如何構建和發送一個插入新原始聯系人和數據的 Intent:
// Gets values from the UI
String name = mContactNameEditText.getText().toString();
String phone = mContactPhoneEditText.getText().toString();
String email = mContactEmailEditText.getText().toString();
String company = mCompanyName.getText().toString();
String jobtitle = mJobTitle.getText().toString();
// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
/*
* Demonstrates adding data rows as an array list associated with the DATA key
*/
// Defines an array list to contain the ContentValues objects for each row
ArrayList contactData = new ArrayList();
/*
* Defines the raw contact row
*/
// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();
// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
// Adds the row to the array
contactData.add(rawContactRow);
/*
* Sets up the phone number data row
*/
// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();
// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);
// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
// Adds the row to the array
contactData.add(phoneRow);
/*
* Sets up the email data row
*/
// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();
// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);
// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
// Adds the row to the array
contactData.add(emailRow);
/*
* Adds the array to the intent's extras. It must be a parcelable object in order to
* travel between processes. The device's contacts app expects its key to be
* Intents.Insert.DATA
*/
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);
數據完整性
聯系人存儲庫包含用戶認為是正確且是最新的重要敏感數據,因此聯系人提供程序具有規定清晰的數據完整性規則。 您有責任在修改聯系人數據時遵守這些規則。 以下列出了其中的重要規則:
務必為您添加的每個ContactsContract.RawContacts行添加一個ContactsContract.CommonDataKinds.StructuredName行。
如果ContactsContract.Data表中的ContactsContract.RawContacts行沒有ContactsContract.CommonDataKinds.StructuredName行,可能會在聚合時引發問題。
務必將新ContactsContract.Data行鏈接到其父ContactsContract.RawContacts行。
如果ContactsContract.Data行未鏈接到ContactsContract.RawContacts,則其在設備的聯系人應用中將處於不可見狀態,而且這可能會導致同步適配器出現問題。
請僅更改您擁有的那些原始聯系人的數據。
請切記,聯系人提供程序所管理的數據通常來自多個不同帳戶類型/在線服務。 您需要確保您的應用僅修改或刪除歸您所有的行的數據,並且僅通過您控制的帳戶類型和帳戶名稱插入數據。
務必使用在ContactsContract及其子類中為權限、內容 URI、URI 路徑、列名稱、MIME 類型以及TYPE值定義的常量。
使用這些常量有助於您避免錯誤。如有任何常量被棄用,您還會從編譯器警告收到通知。
自定義數據行
通過創建和使用自己的自定義 MIME 類型,您可以在ContactsContract.Data表中插入、編輯、刪除和檢索您的自有數據行。 這些行僅限使用ContactsContract.DataColumns中定義的列,但您可以將您自己的類型專用列名稱映射到默認列名稱。 在設備的聯系人應用中,會顯示這些行的數據,但無法對其進行編輯或刪除,用戶也無法添加其他數據。 要允許用戶修改您的自定義數據行,您必須在自己的應用中提供編輯器 Activity。
要顯示您的自定義數據,請提供一個contacts.xml文件,其中須包含一個元素,及其一個或多個子元素。
聯系人提供程序同步適配器
聯系人提供程序專門設計用於處理設備與在線服務之間的聯系人數據同步。 借助同步功能,用戶可以將現有數據下載到新設備,以及將現有數據上傳到新帳戶。 此外,同步還能確保用戶掌握最新數據,無需考慮數據增加和更改的來源。 同步的另一個優點是,即使設備未連接網絡,聯系人數據同樣可用。
雖然您可以通過各種方式實現同步,不過 Android 系統提供了一個插件同步框架,可自動化完成下列任務:
檢查網絡可用性。
根據用戶偏好安排和執行同步。
重啟已停止的同步。
要使用此框架,您需要提供一個同步適配器插件。每個同步適配器都專用於某個服務和內容提供程序,但可以處理同一服務的多個帳戶名稱。 該框架還允許同一服務和提供程序具有多個同步適配器。
同步適配器類和文件
您需要將同步適配器作為AbstractThreadedSyncAdapter的子類進行實現,並作為 Android 應用的一部分進行安裝。系統通過您的應用清單文件中的元素以及由清單文件指向的一個特殊 XML 文件了解有關同步適配器的信息。 該 XML 文件定義在線服務的帳戶類型和內容提供程序的權限,它們共同對適配器進行唯一標識。 用戶為同步適配器的帳戶類型添加一個帳戶,並為與同步適配器同步的內容提供程序啟用同步後,同步適配器才會激活。 激活後,系統將開始管理適配器,並在必要時調用它,以在內容提供程序與服務器之間同步數據。
注:將帳戶類型用作同步適配器標識的一部分讓系統可以發現從同一組織訪問不同服務的同步適配器,並將它們組合在一起。 例如,Google 在線服務的同步適配器都具有相同的帳戶類型com.google。 當用戶向其設備添加 Google 帳戶時,已安裝的所有 Google 服務同步適配器將一起列出;列出的每個同步適配器都與設備上不同的內容提供程序同步。
大多數服務都要求用戶驗證身份後才能訪問數據,為此,Android 系統提供了一個身份驗證框架,該框架與同步適配器框架類似,並且經常與其聯用。 該身份驗證框架使用的插件身份驗證器是AbstractAccountAuthenticator的子類。 身份驗證器通過下列步驟驗證用戶的身份:
收集用戶名、用戶密碼或類似信息(用戶的憑據)。
將憑據發送給服務
檢查服務的回復。
如果服務接受了憑據,身份驗證器便可存儲憑據以供日後使用。 由於插件身份驗證器框架的存在,AccountManager可以提供對身份驗證器支持並選擇公開的任何身份驗證令牌(例如 OAuth2 身份驗證令牌)的訪問。
盡管身份驗證並非必需,但大多數聯系人服務都會使用它。 不過,您不一定要使用 Android 身份驗證框架進行身份驗證。
同步適配器實現
要為聯系人提供程序實現同步適配器,您首先要創建一個包含以下內容的 Android 應用:
一個Service組件,用於響應系統發出的綁定到同步適配器的請求。
當系統想要運行同步時,它會調用服務的onBind()方法,為同步適配器獲取一個IBinder。這樣,系統便可跨進程調用適配器的方法。
作為AbstractThreadedSyncAdapter具體子類實現的實際同步適配器。
此類的作用是從服務器下載數據、從設備上傳數據以及解決沖突。 適配器的主要工作是在方法onPerformSync()中完成的。 必須將此類實例化為單一實例。
Application的子類。
此類充當同步適配器單一實例的工廠。使用onCreate()方法實例化同步適配器,並提供一個靜態“getter”方法,使單一實例返回同步適配器服務的onBind()方法。
可選:一個Service組件,用於響應系統發出的用戶身份驗證請求。
AccountManager會啟動此服務以開始身份驗證流程。 該服務的onCreate()方法會將一個身份驗證器對象實例化。 當系統想要對應用同步適配器的用戶帳戶進行身份驗證時,它會調用該服務的onBind()方法,為該身份驗證器獲取一個IBinder。 這樣,系統便可跨進程調用身份驗證器的方法。
可選:一個用於處理身份驗證請求的AbstractAccountAuthenticator具體子類。
AccountManager就是調用此類所提供的方法向服務器驗證用戶的憑據。 詳細的身份驗證過程會因服務器所采用技術的不同而有很大差異。
用於定義系統同步適配器和身份驗證器的 XML 文件。
之前描述的同步適配器和身份驗證器服務組件都是在應用清單文件中的元素內定義的。 這些元素包含以下用於向系統提供特定數據的子元素:
同步適配器服務的元素指向 XML 文件res/xml/syncadapter.xml。而該文件則指定將與聯系人提供程序同步的 Web 服務的 URI,以及指定該 Web 服務的帳戶類型。
可選:身份驗證器的元素指向 XML 文件res/xml/authenticator.xml。而該文件則指定此身份驗證器所支持的帳戶類型,以及指定身份驗證過程中出現的 UI 資源。 在此元素中指定的帳戶類型必須與為同步適配器指定的帳戶類型相同。
社交流數據
android.provider.ContactsContract.StreamItems表和android.provider.ContactsContract.StreamItemPhotos表管理來自社交網絡的傳入數據。 您可以編寫一個同步適配器,用其將您自己社交網絡中的流數據添加到這些表中,也可以從這些表讀取流數據並將其顯示在您的自有應用中,或者同時采用這兩種方法。 利用這些功能,可以將您的社交網絡服務和應用集成到 Android 的社交網絡體驗之中。
社交流文本
流項目始終與原始聯系人關聯。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID鏈接到原始聯系人的_ID值。 原始聯系人的帳戶類型和帳戶名稱也存儲在流項目行中。
將您的流數據存儲在以下列:
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
必備。與該流項目關聯的原始聯系人對應的用戶帳戶類型。 請記得在插入流項目時設置此值。
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必備。與該流項目關聯的原始聯系人對應的用戶帳戶名稱。 請記得在插入流項目時設置此值。
標識符列
必備。您必須在插入流項目時插入下列標識符列:
android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID:此流項目關聯的聯系人的android.provider.BaseColumns#_ID值。
android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY:此流項目關聯的聯系人的android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY值。
android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID:此流項目關聯的原始聯系人的android.provider.BaseColumns#_ID值。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
可選。存儲可在流項目開頭顯示的摘要信息。
android.provider.ContactsContract.StreamItemsColumns#TEXT
流項目的文本,或為項目來源發布的內容,或是對生成流項目的某項操作的描述。 此列可包含可由fromHtml()渲染的任何格式設置和嵌入式資源圖像。 提供程序可能會截斷或省略較長內容,但它會盡力避免破壞標記。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
一個包含流項目插入時間或更新時間的文本字符串,以從公元紀年開始計算的毫秒數形式表示。 此列由插入或更新流項目的應用負責維護;聯系人提供程序不會自動對其進行維護。
要顯示您的流項目的標識信息,請使用android.provider.ContactsContract.StreamItemsColumns#RES_ICON、android.provider.ContactsContract.StreamItemsColumns#RES_LABEL和android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE鏈接到您的應用中的資源。
android.provider.ContactsContract.StreamItems表還包含供同步適配器專用的列android.provider.ContactsContract.StreamItemsColumns#SYNC1至android.provider.ContactsContract.StreamItemsColumns#SYNC4。
社交流照片
android.provider.ContactsContract.StreamItemPhotos表存儲與流項目關聯的照片。 該表的android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID列鏈接到android.provider.ContactsContract.StreamItems表android.provider.BaseColumns#_ID列中的值。 照片引用存儲在表中的以下列:
android.provider.ContactsContract.StreamItemPhotos#PHOTO列(一個二進制大型對象)。
照片的二進制表示,為便於存儲和顯示,由提供程序調整了尺寸。 此列可用於向後兼容使用它來存儲照片的舊版本聯系人提供程序。 不過,在當前版本中,您不應使用此列來存儲照片, 而應使用android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID或android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(下文對兩者都做了描述)將照片存儲在一個文件內。 此列現在包含可用於讀取的照片縮略圖。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
原始聯系人照片的數字標識符。將此值追加到常量DisplayPhoto.CONTENT_URI,獲取指向單一照片文件的內容 URI,然後調用openAssetFileDescriptor()來獲取照片文件的句柄。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
一個內容 URI,直接指向此行所表示的照片的照片文件。 通過此 URI 調用openAssetFileDescriptor()以獲得照片文件的句柄。
使用社交流表
這些表的工作方式與聯系人提供程序中的其他主表基本相同,不同的是:
這些表需要額外的訪問權限。要讀取它們的數據,您的應用必須具有android.Manifest.permission#READ_SOCIAL_STREAM權限。 要修改它們,您的應用必須具有android.Manifest.permission#WRITE_SOCIAL_STREAM權限。
對於android.provider.ContactsContract.StreamItems表,為每一位原始聯系人存儲的行數有限。 一旦達到該限制,聯系人提供程序即會自動刪除android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP最早的行,為新流項目行騰出空間。 要獲取該限制,請發出對內容 URIandroid.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI的查詢。 您可以將內容 URI 以外的所有其他參數保持設置為null。 查詢會返回一個 Cursor,其中包含一行,並且只有android.provider.ContactsContract.StreamItems#MAX_ITEMS一列。
android.provider.ContactsContract.StreamItems.StreamItemPhotos類定義了android.provider.ContactsContract.StreamItemPhotos的一個子表,其中包含某個流項目的照片行。
社交流交互
通過將聯系人提供程序管理的社交流數據與設備的聯系人應用相結合,可以在您的社交網絡系統與現有聯系人之間建立起有效的連接。 這種結合實現了下列功能:
您可以通過同步適配器讓您的社交網絡服務與聯系人提供程序同步,檢索用戶聯系人的近期 Activity,並將其存儲在android.provider.ContactsContract.StreamItems表和android.provider.ContactsContract.StreamItemPhotos表中,以供日後使用。
除了定期同步外,您還可以在用戶選擇某位聯系人進行查看時觸發您的同步適配器以檢索更多數據。 這樣,您的同步適配器便可檢索該聯系人的高分辨率照片和最近流項目。
通過在設備的聯系人應用以及聯系人提供程序中注冊通知功能,您可以在用戶查看聯系人時收到一個 Intent,並在那時通過您的服務更新聯系人的狀態。 與通過同步適配器執行完全同步相比,此方法可能更快速,占用的帶寬也更少。
用戶可以在查看設備聯系人應用中的聯系人時,將其添加到您的社交網絡服務。 您可以通過“邀請聯系人”功能實現此目的,而該功能則是通過將 Activity 與 XML 文件結合使用來實現的,前者將現有聯系人添加到您的社交網絡,後者為設備的聯系人應用以及聯系人提供程序提供有關您的應用的詳細信息。
流項目與聯系人提供程序的定期同步與其他同步相同。 接下來的兩節介紹如何注冊通知和邀請聯系人。
通過注冊處理社交網絡查看
要注冊您的同步適配器,以便在用戶查看由您的同步適配器管理的聯系人時收到通知,請執行以下步驟:
在您項目的res/xml/目錄中創建一個名為contacts.xml的文件。 如果您已有該文件,可跳過此步驟。
在該文件中添加元素。 如果該元素已存在,可跳過此步驟。
要注冊一項服務,以便在用戶於設備的聯系人應用中打開某位聯系人的詳細信息頁面時通知該服務,請為該元素添加viewContactNotifyService="serviceclass"屬性,其中serviceclass是該服務的完全限定類名,應由該服務接收來自設備聯系人應用的 Intent。 對於這個通知程序服務,請使用一個擴展IntentService的類,以讓該服務能夠接收 Intent。 傳入 Intent 中的數據包含用戶點擊的原始聯系人的內容 URI。 您可以通過通知程序服務綁定到您的同步適配器,然後調用同步適配器來更新原始聯系人的數據。
要注冊需要在用戶點擊流項目或照片(或同時點擊這兩者)時調用的 Activity,請執行以下步驟:
在您項目的res/xml/目錄中創建一個名為contacts.xml的文件。 如果您已有該文件,可跳過此步驟。
在該文件中添加元素。 如果該元素已存在,可跳過此步驟。
要注冊某個 Activity,以處理用戶在設備聯系人應用中點擊某個流項目的操作,請為該元素添加viewStreamItemActivity="activityclass"屬性,其中activityclass是該 Activity 的完全限定類名,應由該 Activity 接收來自設備聯系人應用的 Intent。
要注冊某個 Activity,以處理用戶在設備聯系人應用中點擊某個流照片的操作,請為該元素添加viewStreamItemPhotoActivity="activityclass"屬性,其中activityclass是該 Activity 的完全限定類名,應由該 Activity 接收來自設備聯系人應用的 Intent。
傳入 Intent 包含用戶點擊的項目或照片的內容 URI。 要讓文本項目和照片具有獨立的 Activity,請在同一文件中使用這兩個屬性。
與您的社交網絡服務交互
用戶不必為了邀請聯系人到您的社交網絡網站而離開設備的聯系人應用。 取而代之是,您可以讓設備的聯系人應用發送一個 Intent,將聯系人 邀請到您的 Activity 之一。要設置此功能,請執行以下步驟:
在您項目的res/xml/目錄中創建一個名為contacts.xml的文件。 如果您已有該文件,可跳過此步驟。
在該文件中添加元素。 如果該元素已存在,可跳過此步驟。
添加以下屬性:
inviteContactActivity="activityclass"
inviteContactActionLabel="@string/invite_action_label"activityclass值是應該接收該 Intent 的 Activity 的完全限定類名。invite_action_label值是一個文本字符串,將顯示在設備聯系人應用的Add Connection菜單中。
注:ContactsSource是ContactsAccountType的一個已棄用的標記名稱。
contacts.xml 引用
文件contacts.xml包含一些 XML 元素,這些元素控制您的同步適配器和應用與聯系人應用及聯系人提供程序的交互。 下文對這些元素做了描述。
元素
元素控制您的應用與聯系人應用的交互。 它采用了以下語法:
包含它的文件:
res/xml/contacts.xml
可能包含的內容:
描述:
聲明 Android 組件和 UI 標簽,讓用戶能夠邀請他們的一位聯系人加入社交網絡,在他們的某個社交網絡流更新時通知用戶,以及執行其他操作。
請注意,對的屬性而言,屬性前綴android:並非必需的。
屬性:
inviteContactActivity
您的應用中某個 Activity 的完全限定類名,您想要在用戶於設備的聯系人應用中選擇Add connection時激活該 Activity。
inviteContactActionLabel
Add connection菜單中為inviteContactActivity中指定的 Activity 顯示的文本字符串。 例如,您可以使用字符串“Follow in my network”。您可以為此標簽使用字符串資源標識符。
viewContactNotifyService
您的應用中某項服務的完全限定類名,當用戶查看聯系人時,應由該服務接收通知。 此通知由設備的聯系人應用發送;您的應用可以根據通知將數據密集型操作推遲到必要時再執行。 例如,您的應用對此通知的響應可以是:讀入並顯示聯系人的高分辨率照片和最近的社交流項目。社交流交互部分對此功能做了更詳盡的描述。
viewGroupActivity
您的應用中某個可顯示組信息的 Activity 的完全限定類名。 當用戶點擊設備聯系人應用中的組標簽時,將顯示此 Activity 的 UI。
viewGroupActionLabel
聯系人應用為某個 UI 控件顯示的標簽,用戶可通過該控件查看您的應用中的組。
例如,如果您在設備上安裝了 Google+ 應用,並將 Google+ 與聯系人應用同步,就會看到 Google+ 圈子以組的形式出現在您的聯系人應用的Groups選項卡內。 如果您點擊某個 Google+ 圈子,就會看到該圈子內的聯系人以“組”的形式列出。在該顯示頁面的頂部,您會看到一個 Google+ 圖標;如果您點擊它,控制權將切換給 Google+ 應用。聯系人應用以 Google+ 圖標作為viewGroupActionLabel的值,通過viewGroupActivity來實現此目的。
允許使用字符串資源標識符作為該屬性的值。
viewStreamItemActivity
您的應用中某個 Activity 的完全限定類名,設備的聯系人應用會在用戶點擊原始聯系人的流項目時啟動該 Activity。
viewStreamItemPhotoActivity
您的應用中某個 Activity 的完全限定類名,設備的聯系人應用會在用戶點擊原始聯系人流項目中的照片時啟動該 Activity。
元素
元素控制您的應用的自定義數據行在聯系人應用 UI 中的顯示。它采用了以下語法:
包含它的文件:
描述:
此元素用於讓聯系人應用將自定義數據行的內容顯示為原始聯系人詳細信息的一部分。的每個子元素都代表您的同步適配器向ContactsContract.Data表添加的某個自定義數據行類型。 請為您使用的每個自定義 MIME 類型添加一個元素。 如果您不想顯示任何自定義數據行的數據,則無需添加該元素。
屬性:
android:mimeType
您為ContactsContract.Data表中某個自定義數據行類型定義的自定義 MIME 類型。例如,可將值vnd.android.cursor.item/vnd.example.locationstatus作為記錄聯系人最後已知位置的數據行的自定義 MIME 類型。
android:icon
聯系人應用在您的數據旁顯示的 AndroidDrawable資源。 它用於向用戶指示數據來自您的服務。
android:summaryColumn
從數據行檢索的兩個值中第一個值的列名。該值顯示為該數據行的第一個輸入行。 第一行專用作數據摘要,不過它是可選項。 另請參閱android:detailColumn。
android:detailColumn
從數據行檢索的兩個值中第二個值的列名。該值顯示為該數據行的第二個輸入行。 另請參閱android:summaryColumn。
其他聯系人提供程序功能
除了上文描述的主要功能外,聯系人提供程序還為處理聯系人數據提供了下列有用的功能:
聯系人組
照片功能
聯系人組
聯系人提供程序可以選擇性地為相關聯系人集合添加組數據標簽。 如果與某個用戶帳戶關聯的服務器想要維護組,則與該帳戶的帳戶類型對應的同步適配器應在聯系人提供程序與服務器之間傳送組數據。 當用戶向服務器添加一個新聯系人,然後將該聯系人放入一個新組時,同步適配器必須將這個新組添加到ContactsContract.Groups表中。 原始聯系人所屬的一個或多個組使用ContactsContract.CommonDataKinds.GroupMembershipMIME 類型存儲在ContactsContract.Data表內。
如果您設計的同步適配器會將服務器中的原始聯系人數據添加到聯系人提供程序,並且您不使用組,則需要指示提供程序讓您的數據可見。 在用戶向設備添加帳戶時執行的代碼中,更新聯系人提供程序為該帳戶添加的ContactsContract.Settings行。 在該行中,將Settings.UNGROUPED_VISIBLE列的值設置為 1。執行此操作後,即使您不使用組,聯系人提供程序也會讓您的聯系人數據始終可見。
聯系人照片
ContactsContract.Data表通過 MIME 類型Photo.CONTENT_ITEM_TYPE以行的形式存儲照片。該行的CONTACT_ID列鏈接到其所屬原始聯系人的android.provider.BaseColumns#_ID列。ContactsContract.Contacts.Photo類定義了一個ContactsContract.Contacts子表,其中包含聯系人主要照片(聯系人的主要原始聯系人的主要照片)的照片信息。 同樣,ContactsContract.RawContacts.DisplayPhoto類定義了一個ContactsContract.RawContacts子表,其中包含原始聯系人主要照片的照片信息。
ContactsContract.Contacts.Photo和ContactsContract.RawContacts.DisplayPhoto參考文檔包含檢索照片信息的示例。 並沒有可用來檢索原始聯系人主要縮略圖的實用類,但您可以向ContactsContract.Data表發送查詢,從而通過選定原始聯系人的android.provider.BaseColumns#_ID、Photo.CONTENT_ITEM_TYPE以及IS_PRIMARY列,找到原始聯系人的主要照片行。
聯系人的社交流數據也可能包含照片。這些照片存儲在android.provider.ContactsContract.StreamItemPhotos表中。
藍牙無法正常工作也是Android 5.0當中常出現的一個問題。有的用戶無法配對,有的在建立連接之後依然無法正常工作,還有的會自動斷開連接。An
前段時間群裡兄弟項目中有類似這樣的需求 我看到兄弟受苦受難,於心不忍。又因事不關己,打算高高掛起。正在愛恨糾結之時,日神對我說:沒事多造點輪子,你的人生會有很多
首先我們在開發一個應用之前,特別是一個android應用,首先要考慮這個系統是運行在android版本為2.3的系統上,還是4.0的系統上或者說是支持所有a
下面為控件的實現歷程: 此控件高效,直接使用ondraw繪制,先亮照: 由於Android自身的星星評分控件樣式可以改,但是他的大小不好調整的缺點,只能用small no