Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Contact provider基礎

Android Contact provider基礎

編輯:關於Android編程

Contact provider

Contact provider是一個強大而又靈活的 Android 組件,用於管理設備上有關聯系人數據的中央存儲庫。 Contact provider是您在設備的聯系人應用中看到的數據源,您也可以在自己的應用中訪問其數據,並可在設備與在線服務之間傳送數據。 提供程序儲存有多種數據源,由於它會試圖為每個聯系人管理盡可能多的數據,因此造成其組織結構非常復雜。 為此,該提供程序的 API 包含豐富的協定類和接口,為數據檢索和修改提供便利。

本指南介紹下列內容:

提供程序基本結構 如何從提供程序檢索數據 如何修改提供程序中的數據 如何編寫用於同步服務器數據與聯系人提供程序數據的同步適配器。

Contact provider(聯系人提供程序組織)


聯系人提供程序是 Android 內容提供程序的一個組件。它保留了三種類型的聯系人數據,每一種數據都對應提供程序提供的一個表,如圖 1 所示:
這裡寫圖片描述
圖 1. 聯系人提供程序表結構。

這三個表通常以其協定類的名稱命名。這些類定義表所使用的內容 URI、列名稱及列值相應的常量:

ContactsContract.Contacts表
表示不同聯系人的行,基於聚合的原始聯系人行。

ContactsContract.RawContacts 表
包含聯系人數據摘要的行,針對特定用戶帳戶和類型。

ContactsContract.Data 表
包含原始聯系人詳細信息(例如電子郵件地址或電話號碼)的行。

由 ContactsContract中的協定類表示的其他表是輔助表,Contact provider利用它們來管理其操作,或為設備的聯系人或電話應用中的特定功能提供支持。

Original Contacts(原始聯系人)


一個原始聯系人表示來自某一帳戶類型和帳戶名稱、有關某個聯系人的數據。 由於Contact provider允許將多個在線服務作為某一聯系人的數據源,因此它允許同一聯系人對應多個原始聯系人。 借助支持多個原始聯系人的特性,用戶還可以將某一聯系人在帳戶類型相同的多個帳戶中的數據進行合並。

原始聯系人的大部分數據並不存儲在 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。 請務必使用您擁有或控制的域的域標識符限定您的帳戶類型。 這可以確保您的帳戶類型具有唯一性。 提供聯系人數據的帳戶類型通常關聯有同步適配器,用於與Contact provider進行同步。 DELETED 提供聯系人數據的帳戶類型通常關聯有同步適配器,用於與Contact provider進行同步。 此標志讓Contact provider能夠在內部保留該行,直至同步適配器能夠從服務器刪除該行,然後再從存儲庫中最終刪除該行。

說明
以下是關於 ContactsContract.RawContacts 表的重要說明:

原始聯系人的姓名並不存儲其在 ContactsContract.RawContacts 中的行內,而是存儲在 ContactsContract.Data 表的 ContactsContract.CommonDataKinds.StructuredName 行內。一個原始聯系人在 ContactsContract.Data 表中只有一個該類型的行。 注意:要想在原始聯系人行中使用您自己的帳戶數據,必須先在 AccountManager 中注冊帳戶。 為此,請提示用戶將帳戶類型及其帳戶名稱添加到帳戶列表。 如果您不這樣做,聯系人提供程序將自動刪除您的原始聯系人行。

例如,如果您想讓您的應用為您域名為 com.example.dataservice、基於 Web 的服務保留聯系人數據,並且您的服務的用戶帳戶是 [email protected],則用戶必須先添加帳戶“類型”(com.example.dataservice) 和帳戶“名稱”([email protected]),然後您的應用才能添加原始聯系人行。 您可以在文檔中向用戶解釋這項要求,也可以提示用戶添加類型和名稱,或者同時采用這兩種措施。

Original contact data sources(原始聯系人數據來源)

為理解原始聯系人的工作方式,假設有一位用戶“Emily Dickinson”,她的設備上定義了以下三個用戶帳戶:

[email protected] [email protected] 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。

Data(數據)


如前文所做的說明,原始聯系人的數據存儲在一個 ContactsContract.Data 行中,該行鏈接到原始聯系人的 _ID 值。這使一位原始聯系人可以擁有多個具有相同數據類型的實例,例如電子郵件地址或電話號碼。 例如,如果對應 [email protected] 的“Thomas Higginson”(關聯 Google 帳戶 [email protected] 的 Thomas Higginson 的原始聯系人行)的住宅電子郵件地址為 [email protected],辦公電子郵件地址為 [email protected],則聯系人提供程序會存儲這兩個電子郵件地址行,並將它們都鏈接到原始聯系人。

請注意,這個表中存儲了不同類型的數據。顯示姓名、電話號碼、電子郵件、郵政地址、照片以及網站明細行都可以在 ContactsContract.Data 表中找到。 為便於管理這些數據, ContactsContract.Data 表為一些列使用了描述性名稱,為其他列使用了通用名稱。 使用描述性名稱的列的內容具有相同的含義,與行中數據的類型無關,而使用通用名稱的列的內容則會隨數據類型的不同而具有不同的含義。

Descriptive Column Name(描述性列名稱)

以下是一些描述性列名稱的示例:

RAW_CONTACT_ID
該數據對應的原始聯系人 _ID 列的值。 MIMETYPE
該行中存儲的數據類型,以自定義 MIME(多用途互聯網郵件擴展)類型表示。聯系人提供程序使用了 ContactsContract.CommonDataKinds 子類中定義的 MIME 類型。 這些 MIME 類型為開源類型,可供與聯系人提供程序協作的任何應用或同步適配器使用。 IS_PRIMARY
如果一個原始聯系人可能具有多個這種類型的數據行, IS_PRIMARY 列會標記 包含該類型主要數據的數據行。例如,如果用戶長按某個聯系人的電話號碼,並選擇 Set default,則包含該號碼的 ContactsContract.Data 行會將其 IS_PRIMARY 列設置為一個非零值。

General Column Name(通用列名稱)

有 15 個通用列命名為 DATA1 至 DATA15,可普遍適用;還有四個通用列命名為 SYNC1 至 SYNC4,只應由同步適配器使用。 通用列名稱常量始終有效,與行包含的數據類型無關。

DATA1 列為索引列。聯系人提供程序總是在此列中存儲其預期會成為最頻繁查詢目標的數據。 例如,在一個電子郵件行中,此列包含實際電子郵件地址。

按照慣例,DATA15 為預留列,用於存儲照片縮略圖等二進制大型對象 (BLOB) 數據。

Type-specific column names(類型專用列名稱)

為便於處理特定類型行的列,Contact provider還提供了 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. 類型專用列名稱和通用列名稱。

Type the name of the class dedicated column(類型專用列名稱類)

表 2 列出了最常用的類型專用列名稱類:

表 2. 類型專用列名稱類

映射類 數據類型 備注 ContactsContract.CommonDataKinds.StructuredName 與該數據行關聯的原始聯系人的姓名數據。 一位原始聯系人只有其中一行。 ContactsContract.CommonDataKinds.Photo 與該數據行關聯的原始聯系人的主要照片。 一位原始聯系人只有其中一行。 ContactsContract.CommonDataKinds.Email 與該數據行關聯的原始聯系人的電子郵件地址。 一位原始聯系人可有多個電子郵件地址。 ContactsContract.CommonDataKinds.StructuredPostal 與該數據行關聯的原始聯系人的郵政地址。 一位原始聯系人可有多個郵政地址。 ContactsContract.CommonDataKinds.GroupMembership 將原始聯系人鏈接到Contact provider內其中一組的標識符。 組是帳戶類型和帳戶名稱的一項可選功能。

Contacts(聯系人)

Contact provider通過將所有帳戶類型和帳戶名稱的原始聯系人行合並來形成聯系人。 這可以為顯示和修改用戶針對某一聯系人收集的所有數據提供便利。 聯系人提供程序管理新聯系人行的創建,以及原始聯系人與現有聯系人行的合並。 系統不允許應用或同步適配器添加聯系人,並且聯系人行中的某些列是只讀列。

注:如果您試圖通過 insert() 向聯系人提供程序添加聯系人,會引發一個 UnsupportedOperationException 異常。 如果您試圖更新一個列為“只讀”的列,更新會被忽略。

如果添加的新原始聯系人不匹配任何現有聯系人,Contact provider會相應地創建新聯系人。 如果某個現有原始聯系人的數據發生了變化,不再匹配其之前關聯的聯系人,則提供程序也會執行此操作。 如果應用或同步適配器創建的新原始聯系人“的確”匹配某位現有聯系人,則新原始聯系人將與現有聯系人合並。

Contact provider通過 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 的內容 URI CONTENT_LOOKUP_URI 仍將指向聯系人行,這樣,您就能使用 android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 保持指向“最喜愛”聯系人的鏈接,以及執行其他操作。 該列具有其自己的格式,與 android.provider.BaseColumns#_ID 列的格式無關。

圖 3 顯示的是這三個主要表的相互關系。
這裡寫圖片描述
圖 3. 聯系人表、原始聯系人表與詳細信息表之間的關系。

Data from the synchronization adapter(來自同步適配器的數據)


雖然用戶是直接將聯系人數據輸入到設備中,但這些數據也會通過同步適配器從 Web 服務流入聯系人提供程序中,這些同步適配器可自動化設備與服務之間的數據傳送。 同步適配器在系統控制下在後台運行,它們會調用 ContentResolver 方法來管理數據。

在 Android 中,與同步適配器協作的 Web 服務通過帳戶類型加以標識。 每個同步適配器都與一個帳戶類型協作,但它可以支持該類型的多個帳戶名稱。 原始聯系人數據來源部分對帳戶類型和帳戶名稱做了簡要描述。 下列定義提供了更多詳細信息,並描述了帳戶類型及帳戶名稱與同步適配器及服務之間的關系。

帳戶類型
表示用戶在其中存儲數據的服務。在大多數時候,用戶需要向服務驗證身份。 例如,Google Contacts 是一個以代碼 google.com 標識的帳戶類型。 該值對應於 AccountManager 使用的帳戶類型。

帳戶名稱
**表示某個帳戶類型的特定帳戶或登錄名。**Google Contacts 帳戶與 Google 帳戶相同,都是以電子郵件地址作為帳戶名稱。 其他服務可能使用一個單詞的用戶名或數字 ID。

帳戶類型不必具有唯一性。用戶可以配置多個 Google Contacts 帳戶並將它們的數據下載到聯系人提供程序;如果用戶為個人帳戶名稱和工作帳戶名稱分別設置了一組聯系人,就可能發生這種情況。 帳戶名稱通常具有唯一性。 它們共同標識聯系人提供程序與外部服務之間的特定數據流。

如果您想將服務的數據傳送到聯系人提供程序,則需編寫您自己的同步適配器。

圖 4 顯示的是聯系人提供程序如何融入聯系人數據的流動。 在名為“同步適配器”的方框中,每個適配器都以其帳戶類型命名。
這裡寫圖片描述
圖 4. 聯系人提供程序數據流。

The Required Permissions(所需權限)


想要訪問Contact provider的應用必須請求以下權限:

對一個或多個表的讀取權限
READ_CONTACTS,在 AndroidManifest.xml 中指定,使用 元素作為 。 對一個或多個表的寫入權限
WRITE_CONTACTS,在 AndroidManifest.xml 中指定,使用 元素作為
這些權限不適用於用戶個人資料數據。

請切記,用戶的聯系人數據屬於個人敏感數據。用戶關心其隱私權,因此不希望應用收集有關其自身的數據或其聯系人的數據。 如需權限來訪問其聯系人數據的理由並不充分,用戶可能給您的應用作出差評或干脆拒絕安裝。

User Profile(用戶個人資料)


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”。

(Contacts Provide Metadata)Contacts Provide元數據


Contact Provide管理用於追蹤存儲庫中聯系人數據狀態的數據。 這些有關存儲庫的元數據存儲在各處,其中包括原始聯系人表行、數據表行和聯系人表行、 ContactsContract.Settings 表以及 ContactsContract.SyncState 表。 下表顯示的是每一部分元數據的作用:

表 3. 聯系人提供程序中的元數據

表 列 值 含義 ContactsContract.RawContacts DIRTY “0”:上次同步以來未發生變化。“1”:上次同步以來發生了變化,需要同步回服務器。 標記設備上因發生變化而需要同步回服務器的原始聯系人。 當 Android 應用更新行時,聯系人提供程序會自動設置該值。修改原始聯系人表或數據表的同步適配器應始終向他們使用的內容 URI 追加字符串 CALLER_IS_SYNCADAPTER。 這可以防止提供程序將行標記為已更新。 否則,即使服務器是修改的來源,同步適配器修改仍顯示為本地修改,並會發送到服務器。 ContactsContract.RawContacts VERSION 此行的版本號。 每當行或其相關數據發生變化時,聯系人提供程序都會自動增加此值。 ContactsContract.Data DATA_VERSION 此行的版本號。 每當數據行發生變化時,聯系人提供程序都會自動增加此值。 ContactsContract.RawContacts SOURCE_ID 一個字符串值,用於在創建此原始聯系人的帳戶中對該聯系人進行唯一標識。 當同步適配器創建新原始聯系人時,此列應設置為該原始聯系人在服務器中的唯一 ID。 當 Android 應用創建新原始聯系人時,應將此列留空。 這是為了向同步適配器表明,它應該在服務器上創建新原始聯系人,並獲取 SOURCE_ID 的值。具體地講,對於每個帳戶類型,該源 ID 都必須是唯一的,並且應在所有同步中保持穩定: 1. 唯一:帳戶的每個原始聯系人都必須有自己的源 ID。如果您不強制執行此要求,會在聯系人應用中引發問題。請注意,帳戶類型相同的兩個原始聯系人可以具有相同的源 ID。 例如,允許帳戶 [email protected] 的原始聯系人“Thomas Higginson”與帳戶 [email protected] 的原始聯系人“ThomasHigginson”具有相同的源 ID。 2.穩定:源 ID 是該原始聯系人在在線服務中的數據的永久性組成部分。 例如,如果用戶從應用設置中清除存儲的聯系人數據並重新同步,則恢復的原始聯系人的源 ID 應與以前相同。 如果您不強制執行此要求,快捷方式將停止工作。 ContactsContract.Groups GROUP_VISIBLE “0”:此組中的聯系人在 Android 應用 UI 中不應處於可見狀態。“1”:系統允許此組中的聯系人在應用 UI 中處於可見狀態。 此列用於兼容那些允許用戶隱藏特定組中聯系人的服務器。 ContactsContract.Settings UNGROUPED_VISIBLE “0”:對於此帳戶和帳戶類型,未歸入組的聯系人在 Android 應用 UI 中處於不可見狀態。“1”:對於此帳戶和帳戶類型,未歸入組的聯系人在應用 UI 中處於可見狀態。 默認情況下,如果聯系人的所有原始聯系人都未歸入組,則它們將處於不可見狀態(原始聯系人的組成員身份通過 ContactsContract.Data 表中的一個或多個 ContactsContract.CommonDataKinds.GroupMembership 行指示)。 通過在 ContactsContract.Settings 表行中為帳戶類型和帳戶設置此標志,您可以強制未歸入組的聯系人處於可見狀態。 此標志的一個用途是顯示不使用組的服務器上的聯系人。 ContactsContract.SyncState (所有列) 此表用於存儲同步適配器的元數據。 利用此表,您可以將同步狀態及其他同步相關數據持久地存儲在設備中。

Contacts Access Provider(聯系人提供程序訪問)


本節描述訪問聯系人提供程序中數據的准則,側重於闡述以下內容:

實體查詢。 批量修改。 通過 Intent 執行檢索和修改。 數據完整性。

Discover Entity(查詢實體)

由於Contact Provider表是以層級形式組織,因此對於檢索某一行以及與其鏈接的所有“子”行,往往很有幫助。 例如,要想顯示某位聯系人的所有信息,您可能需要檢索某個 ContactsContract.Contacts 行的所有ContactsContract.RawContacts 行,或者檢索某個 ContactsContract.RawContacts 行的所有 ContactsContract.CommonDataKinds.Email 行。 為便於執行此操作,Contact Provider提供了實體構造,其作用類似於表間的數據庫連接。

實體類似於一個表,由父表及其子表中的選定列組成。 當您查詢實體時,需要根據實體中的可用列提供投影和搜索條件。 結果會得到一個 Cursor,檢索的每個子表行在其中都有一行與之對應。 例如,如果您在 ContactsContract.Contacts.Entity 中查詢某個聯系人姓名以及該姓名所有原始聯系人的所有 ContactsContract.CommonDataKinds.Email 行,您會獲得一個 Cursor,每個 ContactsContract.CommonDataKinds.Email 行在其中都有一行與之對應。

實體簡化了查詢。使用實體時,您可以一次性檢索聯系人或原始聯系人的所有聯系人數據,而不必先通過查詢父表獲得ID,然後通過該 ID 查詢子表。此外,聯系人提供程序可通過單一事務處理實體查詢,這確保了所檢索數據的內部一致性。

注:實體通常不包含父表和子表的所有列。 如果您試圖使用的列名稱並未出現在實體的列名稱常量列表中,則會引發一個 Exception。

以下代碼段說明如何檢索某位聯系人的所有原始聯系人行。該代碼段是一個大型應用的組成部分,包含“主”和“詳”兩個 Activity。 主 Activity 顯示一個聯系人行列表;當用戶選擇一行時,該 Activity 會將其 ID 發送至詳 Activity。 詳 Activity 使用 ContactsContract.Contacts.Entity 顯示與所選聯系人關聯的所有原始聯系人中的所有數據行。

   /*
     * 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 獲取數據,以進行顯示或做進一步處理。

Batch Edit(批量修改)

您應盡可能地通過創建一個 ContentProviderOperation 對象 ArrayList 並調用 applyBatch(),以“批處理模式”在聯系人提供程序中插入、更新和刪除數據。 由於聯系人提供程序是在 applyBatch() 中通過單一事務執行所有操作,因此您的修改絕不會使聯系人存儲庫出現不一致問題。 此外,批量修改還有便於同時插入原始聯系人及其明細數據。

注:要修改單個原始聯系人,可以考慮向設備的聯系人應用發送一個 Intent,而不是在您的應用中處理修改。

Yield Point(屈服點)

一個包含大量操作的批量修改可能會阻斷其他進程,導致糟糕的總體用戶體驗。 要將您想執行的所有修改組織到盡可能少的單獨列表中,同時防止它們阻斷系統,則應為一項或多項操作設置屈服點。 屈服點是一個 ContentProviderOperation 對象,其 isYieldAllowed() 值設置為 true當聯系人提供程序遇到屈服點時,它會暫停其工作,讓其他進程運行,並關閉當前事務。 當提供程序再次啟動時,它會繼續執行 ArrayList 中的下一項操作,並啟動一個新的事務。

屈服點會導致每次調用 applyBatch() 會產生多個事務。因此,您應該為針對一組相關行的最後一項操作設置屈服點。 例如,您應該為一組操作中添加原始聯系人行及其關聯數據行的最後一項操作,或者針對一組與一位聯系人相關的行的最後一項操作設置屈服點。

屈服點也是一個原子操作單元。兩個屈服點之間所有訪問的成功或失敗都將以一個單元的形式出現。 如果您不設置任何屈服點,則最小的原子操作是整個批量操作。 如果您使用了屈服點,則可以防止操作降低系統性能,還可確保一部分操作是原子操作。

Modify References Back(修改向後引用)

當您將一個新原始聯系人行及其關聯的數據行作為一組 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() 方法,該方法是 Contact Manager 示例應用中 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);
    }
}

此外,您還可以利用批處理操作實現optimistic concurrency control,這是一種無需鎖定底層存儲庫便可應用修改事務的控制方法。 要使用此方法,您需要應用事務,然後檢查是否存在可能已同時做出的其他修改。 如果您發現了不一致的修改,請回滾事務並重試。

optimistic concurrency control對於移動設備很有用,因為在移動設備上,同一時間只有一位用戶,並且同時訪問數據存儲庫的情況很少見。 由於未使用鎖定功能,因此不用浪費時間設置鎖定或等待其他事務解除鎖定。

要在更新某個 ContactsContract.RawContacts 行時使用optimistic concurrency control,請按以下步驟操作:
1. 檢索原始聯系人的 VERSION 列以及要檢索的其他數據。
2. 創建一個適合使用 newAssertQuery(Uri) 方法強制執行約束 的 ContentProviderOperation.Builder 對象。對於內容 URI,請使用追加有原始聯系人 android.provider.BaseColumns#_ID 的 RawContacts.CONTENT_URI 。
3. 對於 ContentProviderOperation.Builder 對象,請調用 withValue(),對 VERSION 列與您剛檢索的版本號進行比較。
4. 對於同一 ContentProviderOperation.Builder,請調用 withExpectedCount(),確保此斷言只對一行進行測試。
5. 調用 build() 創建 ContentProviderOperation 對象,然後將此對象添加為要傳遞至 applyBatch() 的 ArrayList 中的第一個對象。
6. 應用批處理事務。

如果在您讀取原始聯系人行到您試圖對其進行修改這段時間有另一項操作更新了該行,“Assert”ContentProviderOperation 將會失敗,系統將終止整個批處理操作。 此情況下,您可以選擇重新執行批處理操作,或執行其他某操作。

以下代碼段演示如何在使用 CursorLoader 查詢一位原始聯系人後創建一個“Assert” 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 通過設備的聯系人應用訪問Contact Provider時,您無需自行編寫用於訪問該提供程序的 UI 或代碼。 您也無需請求對提供程序的讀取或寫入權限。 設備的聯系人應用可以將聯系人讀取權限授予給您,而且您是通過另一個應用對該提供程序進行修改,不需要擁有寫入權限。通過Intent訪問數據參考Android Content Provider基礎

表 4 匯總了您為可用任務使用的操作、MIME 類型以及數據值,ContactsContract.Intents.Insert 參考文檔列出了您可用於putExtra() 的 Extra 值:

表 4. Contact Provider Intent。

ACTION_PICK 操作 數據 MIME 類型 備注 從列表中選取一位聯系人 ACTION_PICK 下列值之一:1. Contacts.CONTENT_URI,顯示聯系人列表。 2. Phone.CONTENT_URI,顯示原始聯系人的電話號碼列表。 3. StructuredPostal.CONTENT_URI,顯示原始聯系人的郵政地址列表。4.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);

數據完整性

聯系人存儲庫包含用戶認為是正確且是最新的重要敏感數據,因此Contact Provider具有規定清晰的數據完整性規則。 您有責任在修改聯系人數據時遵守這些規則。 以下列出了其中的重要規則:

務必為您添加的每個 ContactsContract.RawContacts 行添加一個 ContactsContract.CommonDataKinds.StructuredName 行。

如果ContactsContract.Data 表中的 ContactsContract.RawContacts 行沒有 ContactsContract.CommonDataKinds.StructuredName 行,可能會在聚合時引發問題。

務必將新 ContactsContract.Data 行鏈接到其父 ContactsContract.RawContacts 行。

如果 ContactsContract.Data 行未鏈接到 ContactsContract.RawContacts,則其在設備的聯系人應用中將處於不可見狀態,而且這可能會導致同步適配器出現問題。

請僅更改您擁有的那些原始聯系人的數據。

請切記,Contact Provider所管理的數據通常來自多個不同帳戶類型/在線服務。 您需要確保您的應用僅修改或刪除歸您所有的行的數據,並且僅通過您控制的帳戶類型和帳戶名稱插入數據。

務必使用在 ContactsContract 及其子類中為權限、內容 URI、URI 路徑、列名稱、MIME 類型以及 TYPE 值定義的常量。

使用這些常量有助於您避免錯誤。如有任何常量被棄用,您還會從編譯器警告收到通知。

自定義數據行

通過創建和使用自己的自定義 MIME 類型,您可以在 ContactsContract.Data 表中插入、編輯、刪除和檢索您的自有數據行。 這些行僅限使用 ContactsContract.DataColumns 中定義的列,但您可以將您自己的類型專用列名稱映射到默認列名稱。 在設備的聯系人應用中,會顯示這些行的數據,但無法對其進行編輯或刪除,用戶也無法添加其他數據。 要允許用戶修改您的自定義數據行,您必須在自己的應用中提供編輯器 Activity。

要顯示您的自定義數據,請提供一個 contacts.xml 文件,其中須包含一個 元素,及其一個或多個 子元素。 element 部分對此做了更詳盡的描述。

Contacts Provider Sync Adapters(同步適配器)


要為Contact Provider 實現同步適配器,您首先要創建一個包含以下內容的 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 資源。 在此元素中指定的帳戶類型必須與為同步適配器指定的帳戶類型相同。

Social Data Exchange(社交流數據)


android.provider.ContactsContract.StreamItems 表和 android.provider.ContactsContract.StreamItemPhotos 表管理來自社交網絡的傳入數據。 您可以編寫一個同步適配器,用其將您自己社交網絡中的流數據添加到這些表中,也可以從這些表讀取流數據並將其顯示在您的自有應用中,或者同時采用這兩種方法。 利用這些功能,可以將您的社交網絡服務和應用集成到 Android 的社交網絡體驗之中。

Social exchange text(社交流文本)

流項目始終與原始聯系人關聯。 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。

Social Exchange Photos(社交流照片)

android.provider.ContactsContract.StreamItemPhotos 表存儲與流項目關聯的照片。 該表的 android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID列鏈接到 android.provider.ContactsContract.StreamItems 表 android.provider.BaseColumns#_ID 列中的值。 照片引用存儲在表中的以下列:

android.provider.ContactsContract.StreamItemPhotos#PHOTO 列(一個二進制大型對象)。
照片的二進制表示,為便於存儲和顯示,由提供程序調整了尺寸。 此列可用於向後兼容使用它來存儲照片的舊版本Contact Provider。 不過,在當前版本中,您不應使用此列來存儲照片, 而應使用 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() 以獲得照片文件的句柄。

Use table club AC(使用社交流表)

這些表的工作方式與聯系人提供程序中的其他主表基本相同,不同的是:

這些表需要額外的訪問權限。要讀取它們的數據,您的應用必須具有 android.Manifest.permission#READ_SOCIAL_STREAM 權限。 要修改它們,您的應用必須具有 android.Manifest.permission#WRITE_SOCIAL_STREAM 權限。

對於 android.provider.ContactsContract.StreamItems 表,為每一位原始聯系人存儲的行數有限。 一旦達到該限制,聯系人提供程序即會自動刪除 android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP 最早的行,為新流項目行騰出空間。 要獲取該限制,請發出對內容 URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI 的查詢。 您可以將內容 URI 以外的所有其他參數保持設置為 null。 查詢會返回一個 Cursor,其中包含一行,並且只有 android.provider.ContactsContract.StreamItems#MAX_ITEMS 一列。
android.provider.ContactsContract.StreamItems.StreamItemPhotos 類定義了 android.provider.ContactsContract.StreamItemPhotos 的一個子表,其中包含某個流項目的照片行。

Social interaction and communication(社交流交互)

通過將聯系人提供程序管理的社交流數據與設備的聯系人應用相結合,可以在您的社交網絡系統與現有聯系人之間建立起有效的連接。 這種結合實現了下列功能:

您可以通過同步適配器讓您的社交網絡服務與Contact Provider同步,檢索用戶聯系人的近期 Activity,並將其存儲在 android.provider.ContactsContract.StreamItems 表和 android.provider.ContactsContract.StreamItemPhotos 表中,以供日後使用。

除了定期同步外,您還可以在用戶選擇某位聯系人進行查看時觸發您的同步適配器以檢索更多數據。 這樣,您的同步適配器便可檢索該聯系人的高分辨率照片和最近流項目。

通過在設備的聯系人應用以及Contact Provider中注冊通知功能,您可以在用戶查看聯系人時收到一個 Intent,並在那時通過您的服務更新聯系人的狀態。 與通過同步適配器執行完全同步相比,此方法可能更快速,占用的帶寬也更少。

用戶可以在查看設備聯系人應用中的聯系人時,將其添加到您的社交網絡服務。 您可以通過“邀請聯系人”功能實現此目的,而該功能則是通過將 Activity 與 XML 文件結合使用來實現的,前者將現有聯系人添加到您的社交網絡,後者為設備的聯系人應用以及Contact Provider提供有關您的應用的詳細信息。

通過注冊處理社交網絡查看

要注冊您的同步適配器,以便在用戶查看由您的同步適配器管理的聯系人時收到通知,請執行以下步驟:

在您項目的 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 類型。

android:icon
聯系人應用在您的數據旁顯示的 Android Drawable資源。 它用於向用戶指示數據來自您的服務。

android:summaryColumn
從數據行檢索的兩個值中第一個值的列名。該值顯示為該數據行的第一個輸入行。 第一行專用作數據摘要,不過它是可選項。

android:detailColumn
從數據行檢索的兩個值中第二個值的列名。該值顯示為該數據行的第二個輸入行。

其他聯系人提供程序功能


除了上文描述的主要功能外,聯系人提供程序還為處理聯系人數據提供了下列有用的功能:

聯系人組 照片功能

聯系人組

Contact Provider可以選擇性地為相關聯系人集合添加組數據標簽。 如果與某個用戶帳戶關聯的服務器想要維護組,則與該帳戶的帳戶類型對應的同步適配器應在聯系人提供程序與服務器之間傳送組數據。 當用戶向服務器添加一個新聯系人,然後將該聯系人放入一個新組時,同步適配器必須將這個新組添加到 ContactsContract.Groups 表中。 原始聯系人所屬的一個或多個組使用 ContactsContract.CommonDataKinds.GroupMembership MIME 類型存儲在 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 表中。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved