聯絡人供應程式

聯絡人提供者是強大且靈活的 Android 元件,可管理裝置的聯絡人資料集中式存放區。聯絡人提供者是裝置聯絡人應用程式中顯示的資料來源,您也可以在自己的應用程式中存取其資料,並在裝置和線上服務之間傳輸資料。供應商會支援多種資料來源,並盡可能為每位使用者管理最多資料,因此其組織結構相當複雜。因此,供應器的 API 包含一組完整的合約類別和介面,可協助您擷取及修改資料。

本指南將說明以下內容:

  • 基本提供者結構。
  • 如何從供應器擷取資料。
  • 如何修改供應器中的資料。
  • 如何編寫同步處理器,以便將伺服器中的資料同步至聯絡資訊提供者。

本指南假設您瞭解 Android 內容供應器的基本概念。如要進一步瞭解 Android 內容供應器,請參閱「 內容供應器基本概念」指南。

聯絡人供應程式機構

聯絡人供應程式是 Android 內容供應器元件。它會維護三種類型的個人資料,每個資料都對應至供應商提供的資料表,如圖 1 所示:

圖 1. 聯絡人供應器表格結構。

這三個資料表通常會以其合約類別的名稱來命名。這些類別會定義資料表使用的內容 URI、資料欄名稱和資料欄值的常數:

ContactsContract.Contacts 資料表
根據原始聯絡人資料列的匯總資料,列出代表不同人的資料列。
ContactsContract.RawContacts 表格
列包含特定使用者帳戶和類型的人物資料摘要。
ContactsContract.Data 表格
列包含原始聯絡人的詳細資料,例如電子郵件地址或電話號碼。

ContactsContract 中由合約類別代表的其他資料表是輔助資料表,可讓聯絡人提供者用於管理其作業,或支援裝置聯絡人或電話應用程式中的特定功能。

原始聯絡人

原始聯絡人代表來自單一帳戶類型和帳戶名稱的使用者資料。由於聯絡人提供者允許使用多項線上服務做為個人的資料來源,因此聯絡人提供者可以為同一位聯絡人允許多個原始聯絡人。使用者也可以透過多個原始聯絡人,將同一個帳戶類型的多個帳戶中某人提供的資料合併。

原始聯絡人的大部分資料不會儲存在 ContactsContract.RawContacts 資料表中。而是儲存在 ContactsContract.Data 資料表的一或多個資料列中。每個資料列都有一個 Data.RAW_CONTACT_ID 欄,其中包含其父項 ContactsContract.RawContacts 資料列的 RawContacts._ID 值。

重要原始聯絡人欄

表 1 已列出 ContactsContract.RawContacts 資料表中的重要資料欄。請參閱表格後方的附註:

表 1. 重要原始聯絡人欄。

資料欄名稱 使用 附註
ACCOUNT_NAME 此原始聯絡人的來源帳戶類型名稱。舉例來說,Google 帳戶的帳戶名稱是裝置擁有者的其中一個 Gmail 地址。詳情請參閱 ACCOUNT_TYPE 的下一個項目。 這個名稱的格式會依帳戶類型而異。不一定是電子郵件地址。
ACCOUNT_TYPE 這個原始聯絡人的來源帳戶類型。舉例來說,Google 帳戶的帳戶類型為 com.google。請務必使用您擁有或控管的網域識別碼,確認帳戶類型。這麼做可以確保帳戶類型未重複。 提供聯絡人資料的帳戶類型通常會搭配使用與聯絡資訊提供者同步的同步處理轉接器。
DELETED 原始聯絡人的「已刪除」標記。 這個標記可讓聯絡人提供者在同步處理轉接程式可從伺服器刪除資料列,並最終從存放區刪除資料列之前,在內部維護資料列。

附註

以下是 ContactsContract.RawContacts 資料表的重要注意事項:

  • 原始聯絡人的名稱不會儲存在 ContactsContract.RawContacts 的資料列中。而是儲存在 ContactsContract.Data 資料表的 ContactsContract.CommonDataKinds.StructuredName 列中。原始聯絡人的 ContactsContract.Data 資料表中只有這一列。
  • 注意:如要在原始聯絡人資料列中使用自己的帳戶資料,必須先在 AccountManager 中註冊該資料。為此,請提示使用者將帳戶類型和帳戶名稱加入帳戶清單。如果您未執行這項操作,聯絡資訊提供者會自動刪除原始聯絡資訊資料列。

    舉例來說,如果您希望應用程式為網頁服務 (網域為 com.example.dataservice) 維護聯絡人資料,而服務的使用者帳戶為 becky.sharp@dataservice.example.com,則使用者必須先新增帳戶「類型」(com.example.dataservice) 和帳戶「名稱」(becky.smart@dataservice.example.com),應用程式才能新增原始聯絡人資料列。您可以在說明文件中向使用者說明這項規定,也可以提示使用者新增類型和名稱,或兩者皆行。我們會在下一節詳細說明帳戶類型和帳戶名稱。

原始聯絡資料來源

如要瞭解原始聯絡人的運作方式,請考慮使用「小艾 Dickinson」這位使用者的裝置,為其定義下列三個使用者帳戶:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Twitter 帳戶「belle_of_amherst」

這位使用者已在「帳戶」設定中,為這三個帳戶啟用「同步處理聯絡人」功能。

假設 Emily Dickinson 開啟瀏覽器視窗,以 emily.dickinson@gmail.com 身分登入 Gmail,開啟聯絡人並新增「Thomas Higginson」。之後,她以 emilyd@gmail.com 身分登入 Gmail,並傳送電子郵件給「Thomas Higginson」,系統會自動將他新增為聯絡人。她在 Twitter 也追蹤了「冒號_tom」(Thomas Higginson 的 Twitter ID)。

聯絡人供應器會根據這項工作建立三個原始聯絡人:

  1. emily.dickinson@gmail.com 相關聯的「Thomas Higginson」原始聯絡人。使用者帳戶類型為 Google。
  2. emilyd@gmail.com 相關聯的「Thomas Higginson」第二個原始聯絡人。 使用者帳戶類型也是 Google。即使名稱與先前的名稱相同,仍會有第二個原始聯絡人,因為該聯絡人是為了不同的使用者帳戶而新增。
  3. 第三個原始聯絡人為「Thomas Higginson」,與「belle_of_amherst」相關。使用者帳戶類型為 Twitter。

資料

如先前所述,原始聯絡人的資料會儲存在與原始聯絡人的 _ID 值連結的 ContactsContract.Data 資料列中。這樣一來,單一原始聯絡人就能擁有多個相同類型的資料例項,例如電子郵件地址或電話號碼。舉例來說,如果 emilyd@gmail.com 的「Thomas Higginson」(與 Google 帳戶 emilyd@gmail.com 相關聯的 Thomas Higginson 原始聯絡資料列) 的住家地址為 thigg@gmail.com 和公司電子郵件地址 thomas.higginson@gmail.com,聯絡人提供者會儲存兩個電子郵件地址列,並將這些列連結至原始聯絡人。

請注意,不同類型的資料都儲存在這個單一資料表中。顯示名稱、電話號碼、電子郵件、郵遞地址、相片和網站詳細資料列都位於 ContactsContract.Data 表格中。為協助您進行管理,ContactsContract.Data 資料表有一些資料欄採用描述性名稱,其他資料欄則使用一般名稱。無論資料列中的資料類型為何,描述性名稱資料欄的內容都具有相同的含義,而通用名稱資料欄的內容則會因資料類型而有所不同。

描述性欄名稱

以下是幾個描述性資料欄名稱的範例:

RAW_CONTACT_ID
這項資料的原始聯絡人資料中 _ID 欄的值。
MIMETYPE
這個資料列中儲存的資料類型,以自訂 MIME 類型表示。聯絡人提供者會使用 ContactsContract.CommonDataKinds 子類別中定義的 MIME 類型。這些 MIME 類型為開放原始碼,可供任何與聯絡資訊提供者搭配使用的應用程式或同步處理轉接程式使用。
IS_PRIMARY
如果原始聯絡人的這類資料列可能出現多次,IS_PRIMARY 欄會標示含有該類型主要資料的資料列。舉例來說,如果使用者長按聯絡人的電話號碼,然後選取「設定預設值」,則含有該號碼的 ContactsContract.Data 資料列就會將其 IS_PRIMARY 欄設為非零值。

一般資料欄名稱

有 15 個通用資料欄,名稱為 DATA1DATA15,一般可供使用,另外還有四個通用資料欄 SYNC1SYNC4,應僅供同步處理轉接器使用。無論資料列包含哪種資料類型,泛型資料欄名稱常數一律有效。

DATA1 欄已建立索引。聯絡人供應工具一律會使用這個欄位,用於供應工具預期會成為查詢最常見目標的資料。舉例來說,在電子郵件資料列中,這個欄位會包含實際的電子郵件地址。

依照慣例,欄 DATA15 是用來儲存相片縮圖等二進位大型物件 (BLOB) 資料。

類型專屬的資料欄名稱

為方便使用特定資料列類型的資料欄,Contacts 供應器也提供類型專屬的資料欄名稱常數,這些常數是在 ContactsContract.CommonDataKinds 的子類別中定義。常數只會為相同的資料欄名稱提供不同的常數名稱,方便您存取特定類型資料列中的資料。

舉例來說,ContactsContract.CommonDataKinds.Email 類別會為具有 MIME 類型 Email.CONTENT_ITEM_TYPEContactsContract.Data 資料列定義類型專屬的資料欄名稱常數。這個類別包含電子郵件地址欄的常數 ADDRESSADDRESS 的實際值是「data1」,與資料欄的一般名稱相同。

注意:請勿使用含有提供者預先定義 MIME 類型之一的資料列,將您自己的自訂資料新增至 ContactsContract.Data 資料表。否則可能會導致資料遺失或供應器發生故障。舉例來說,您不應在 DATA1 欄中新增 MIME 類型 Email.CONTENT_ITEM_TYPE 的資料列,因為該欄應包含電子郵件地址,而非使用者名稱。如果您為資料列使用自訂 MIME 類型,則可以自由定義自訂類型專屬的欄名稱,並自由使用這些欄。

圖 2 顯示說明性資料欄和資料欄在 ContactsContract.Data 列中顯示的方式,以及類型專屬的資料欄名稱如何「重疊」一般資料欄名稱。

如何將特定類型的資料欄名稱對應至一般資料欄名稱

圖 2. 類型專屬的資料欄名稱和一般資料欄名稱。

類型特定的資料欄名稱類別

表 2 列出最常用的類型特定欄名稱類別:

表 2. 類型特定的資料欄名稱類別

對應類別 資料類型 附註
ContactsContract.CommonDataKinds.StructuredName 與這個資料列相關聯的原始聯絡人姓名資料。 原始聯絡人只有一個這類資料列。
ContactsContract.CommonDataKinds.Photo 與這個資料列相關聯的原始聯絡人的主相片。 原始聯絡人只有一個這類資料列。
ContactsContract.CommonDataKinds.Email 與這個資料列相關聯的原始聯絡資訊電子郵件地址。 原始聯絡人可以有多個電子郵件地址。
ContactsContract.CommonDataKinds.StructuredPostal 與這個資料列相關聯的原始聯絡人的郵寄地址。 原始聯絡人可以有多個郵寄地址。
ContactsContract.CommonDataKinds.GroupMembership 將原始聯絡人連結至聯絡資訊供應器中其中一個群組的 ID。 群組是帳戶類型和帳戶名稱的選用功能。如需詳細說明,請參閱「聯絡人群組」一節。

聯絡人

聯絡人供應程式會將所有帳戶類型和帳戶名稱的原始聯絡人資料列合併,形成聯絡人。這有助於顯示及修改使用者為某人收集的所有資料。聯絡人供應程式會管理新聯絡人資料列的建立作業,以及原始聯絡人與現有聯絡人資料列的匯總作業。應用程式和同步處理轉接器都無法新增聯絡人,且聯絡人資料列中的部分欄為唯讀。

注意:如果您嘗試使用 insert() 將聯絡人新增至聯絡人供應程式,就會發生 UnsupportedOperationException 例外狀況。如果您嘗試更新列為「唯讀」的資料欄,系統會忽略更新作業。

聯絡資訊提供者會在新增不符合任何現有聯絡資訊的原始聯絡資訊時,建立新聯絡資訊。如果現有的原始聯絡人資料發生變更,導致不再與先前所附聯絡人相符,供應商也會執行這項操作。如果應用程式或同步轉換介面建立與現有聯絡人「相符」的新原始聯絡人,系統會將新的原始聯絡人匯總至現有聯絡人。

聯絡人供應器會透過 Contacts 資料表中聯絡人資料列的 _ID 欄,將聯絡人資料列連結至原始聯絡人資料列。原始聯絡人資料表 ContactsContract.RawContactsCONTACT_ID 欄包含與每個原始聯絡人資料表列相關聯的聯絡人資料表列 _ID 值。

ContactsContract.Contacts 表格也有 LOOKUP_KEY 欄,這是與聯絡人資料列的「永久」連結。由於聯絡人提供者會自動維護聯絡人,因此可能會在匯總或同步處理時變更聯絡人資料列的 _ID 值。即使發生這種情況,與聯絡人的 LOOKUP_KEY 結合的內容 URI CONTENT_LOOKUP_URI 仍會指向聯絡人資料列,因此您可以使用 LOOKUP_KEY 維持「最愛」聯絡人的連結等等。這個資料欄有其專屬格式,與 _ID 欄的格式無關。

圖 3 顯示三個主要資料表之間的關聯。

聯絡人供應程式的主要資料表

圖 3. 聯絡人、原始聯絡人和詳細資料表格關係。

注意: 如果您將應用程式發布至 Google Play 商店,或是應用程式在搭載 Android 10 (API 級別 29) 以上版本的裝置上執行,請注意,部分聯絡資料欄位和方法已淘汰。

在上述條件下,系統會定期清除寫入這些資料欄位的所有值:

用於設定上述資料欄位的 API 也已淘汰:

此外,下列欄位也不再傳回常用聯絡人。請注意,只有在聯絡人屬於特定資料類型時,這些欄位才會影響聯絡人的排名。

如果您的應用程式會存取或更新這些欄位或 API,請使用其他方法。舉例來說,您可以使用私人內容供應器或應用程式或後端系統中儲存的其他資料,滿足特定用途。

如要確認應用程式的功能不會受到這項變更的影響,您可以手動清除這些資料欄位。如要執行此操作,請在搭載 Android 4.1 (API 級別 16) 以上版本的裝置上執行下列 ADB 指令:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

來自同步轉換介面的資料

使用者可直接在裝置上輸入聯絡人資料,但資料也會透過同步處理器從網路服務流入聯絡人提供者,這可自動在裝置和服務之間傳輸資料。同步處理轉接器會在系統的控管下在背景執行,並呼叫 ContentResolver 方法來管理資料。

在 Android 中,同步處理轉接器所使用的網路服務會透過帳戶類型識別。每個同步處理轉接器都會與一種帳戶類型搭配運作,但可支援該類型的多個帳戶名稱。原始聯絡人資料來源一節簡要說明帳戶類型和帳戶名稱。以下定義提供更多詳細資訊,並說明帳戶類型和名稱與同步處理轉接器和服務的關聯性。

帳戶類型
識別使用者要在當中儲存資料的服務。在大多數的情況下,使用者需要透過服務進行驗證。舉例來說,Google 聯絡人是一種帳戶類型,可透過 google.com 代碼識別。這個值對應至 AccountManager 使用的帳戶類型。
帳戶名稱
用於識別帳戶類型的特定帳戶或登入資訊。Google 聯絡人帳戶與 Google 帳戶相同,後者會使用電子郵件地址做為帳戶名稱。其他服務可使用單一字詞使用者名稱或數字 ID。

帳戶類型不必是唯一值。使用者可以設定多個 Google 聯絡人帳戶,並將資料下載至聯絡資訊供應器。如果使用者有一個個人帳戶名稱的個人聯絡資訊,以及另一個工作帳戶名稱的聯絡資訊,就可能發生這種情況。帳戶名稱通常會是唯一的。共同識別聯絡人提供者和外部服務之間的特定資料流程。

如果您想將服務的資料轉移至聯絡人提供者,就必須自行編寫同步處理轉接器。如需進一步的詳細說明,請參閱「聯絡人提供者同步處理轉接程式」一節。

圖 4 顯示聯絡人提供者如何融入人物資料的資料流程。在標示為「同步處理轉接器」的方塊中,每個轉接器都會標示帳戶類型。

人物資料流

圖 4. 聯絡人供應程式的資料流程。

所需權限

如要存取聯絡資訊供應器,應用程式必須要求下列權限:

具備一或多個資料表的讀取權限
READ_CONTACTS,在 AndroidManifest.xml 中指定, <uses-permission> 元素為 <uses-permission android:name="android.permission.READ_CONTACTS">
一或多個資料表的寫入權限
WRITE_CONTACTS,在 AndroidManifest.xml 中指定, <uses-permission> 元素為 <uses-permission android:name="android.permission.WRITE_CONTACTS">

這些權限不會擴及使用者個人資料資料。關於使用者個人資料及其必要權限,請參閱下一節「使用者個人資料」一節。

請注意,使用者的聯絡人資料屬於個人和機密資料。使用者擔心自己的隱私安全,因此不希望應用程式收集有關自己或聯絡人的資料。如果使用者不清楚您為何需要存取聯絡人資料的權限,可能會給予應用程式低評分,或乾脆拒絕安裝。

使用者個人資料

ContactsContract.Contacts 資料表只有一列,其中包含裝置使用者的個人資料。這項資料是說明裝置的 user,而非使用者的聯絡人。設定檔聯絡人資料列會連結至使用設定檔的每個系統的原始聯絡人資料列。每個設定檔原始聯絡資料列都可以有多個資料列。ContactsContract.Profile 類別提供用於存取使用者個人資料的常數。

您必須具備特殊權限,才能存取使用者設定檔。除了用於讀取和寫入的 READ_CONTACTSWRITE_CONTACTS 權限之外,您還需要分別具備 android.Manifest.permission#READ_PROFILE 和 android.Manifest.permission#WRITE_PROFILE 權限,才能存取使用者設定檔。

提醒您,請務必將使用者的個人資料視為敏感資料。權限 android.Manifest.permission#READ_PROFILE 可讓您存取裝置使用者的個人識別資料。請務必在應用程式說明中告知使用者,您需要使用者設定檔存取權的原因。

如要擷取含有使用者個人資料的聯絡人列,請呼叫 ContentResolver.query()。將內容 URI 設為 CONTENT_URI,而不要提供任何選取條件。您也可以使用這個內容 URI 做為擷取原始聯絡人或設定檔資料的基礎 URI。例如,以下程式碼片段會擷取設定檔的資料:

Kotlin

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Java

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

注意:如果您擷取多個聯絡人資料列,並想判斷其中一個是否為使用者設定檔,請測試該資料列的 IS_USER_PROFILE 欄。如果聯絡人是使用者設定檔,這個欄會設為「1」。

聯絡人供應程式中繼資料

聯絡人供應程式會管理用於追蹤存放區中聯絡人資料狀態的資料。這類存放區的中繼資料會儲存在不同位置,包括「原始聯絡人」、「資料」和「聯絡人」表格列、ContactsContract.Settings 資料表和 ContactsContract.SyncState 資料表。下表顯示前述各項中繼資料的效果:

表 3. 聯絡人供應程式中的中繼資料

表格 Column 意義
ContactsContract.RawContacts DIRTY 「0」:自上次同步處理後未變更。 標示在裝置上變更的未經處理聯絡人,並必須同步回伺服器。當 Android 應用程式更新資料列時,Contacts 提供者會自動設定這個值。

修改原始聯絡人或資料表的同步處理轉接器,應一律將字串 CALLER_IS_SYNCADAPTER 附加至所使用的內容 URI。這樣可避免供應者將資料列標示為髒資料。否則,同步處理轉接器的修改內容會視為本機修改內容,並且會傳送至伺服器,即使伺服器是修改內容的來源也一樣。

「1」- 自上次同步後有所變更,需要同步回伺服器。
ContactsContract.RawContacts VERSION 此列的版本號碼。 每當資料列或其相關資料有所變更時,聯絡人提供者會自動遞增這個值。
ContactsContract.Data DATA_VERSION 這個資料列的版本號碼。 只要資料列有變更,Contacts 供應器就會自動遞增這個值。
ContactsContract.RawContacts SOURCE_ID 這個字串值可用於識別此原始聯絡人與建立該聯絡人的帳戶。 當同步處理器建立新的原始聯絡資訊時,這個欄應設為原始聯絡資訊的伺服器專屬 ID。當 Android 應用程式建立新的原始聯絡人時,應用程式應將此欄留空。這會向同步處理轉接器發出訊號,指出應在伺服器上建立新的原始聯絡人,並取得 SOURCE_ID 的值。

特別是,來源 ID 必須是每個帳戶類型的唯一值,且在同步處理期間應保持穩定:

  • 不重複:帳戶的每個原始聯絡人都必須有專屬的來源 ID。如果您未強制執行此設定,就會導致聯絡人應用程式發生問題。請注意,同一個帳戶類型的兩個原始聯絡人可能具有相同的來源 ID。舉例來說,帳戶 emily.dickinson@gmail.com 的原始聯絡人「Thomas Higginson」可以與帳戶 emilyd@gmail.com 的原始聯絡人「Thomas Higginson」使用相同的來源 ID。
  • 穩定版:來源 ID 是原始聯絡人的線上服務資料的永久部分。舉例來說,如果使用者從「應用程式」設定清除聯絡人儲存空間,並重新進行同步處理,則還原的原始聯絡人應與先前的來源 ID 相同。如未強制執行,捷徑就會停止運作。
ContactsContract.Groups GROUP_VISIBLE 「0」:這個群組中的聯絡人應不會顯示在 Android 應用程式 UI 中。 這個欄位可與允許使用者隱藏特定群組聯絡人的伺服器相容。
「1」:這個群組中的聯絡人可在應用程式 UI 中顯示。
ContactsContract.Settings UNGROUPED_VISIBLE 「0」:Android 應用程式 UI 不會顯示不屬於群組的聯絡人。 根據預設,如果聯絡人的原始聯絡人皆未屬於任何群組,該聯絡人就會隱藏 (原始聯絡人的群組成員資格,可由 ContactsContract.Data 資料表中一或多個 ContactsContract.CommonDataKinds.GroupMembership 資料列表示)。您可以在帳戶類型和帳戶的 ContactsContract.Settings 表格資料列中設定這個標記,強制顯示沒有群組的聯絡人。這個標記的用途之一,是顯示來自不使用群組的伺服器的聯絡人。
「1」:以這個帳戶和帳戶類型來說,應用程式 UI 可以看到不屬於群組的聯絡人。
ContactsContract.SyncState (全部) 使用這個表格儲存同步轉接程式的中繼資料。 您可以使用這個表格將同步處理狀態和其他同步相關資料永久儲存在裝置中。

聯絡人提供者存取權

本節說明如何存取聯絡人供應商的資料,並著重於以下事項:

  • 實體查詢。
  • 批次修改。
  • 使用意圖擷取及修改資料。
  • 資料完整性。

如要進一步瞭解如何透過同步處理器進行修改,請參閱「聯絡資訊提供者同步處理器」一節。

查詢實體

由於聯絡人提供者資料表是按階層分類,因此擷取與此資料表相關的資料列和所有「子項」資料列,通常會很有幫助。舉例來說,如要顯示某人所有的資訊,您可能會想針對單一 ContactsContract.Contacts 列,擷取所有 ContactsContract.RawContacts 列,或是針對單一 ContactsContract.RawContacts 列,擷取所有 ContactsContract.CommonDataKinds.Email 列。為方便這項作業,聯絡資訊供應器會提供實體結構,這類似於資料表之間的資料庫彙整作業。

實體就像是資料表,由父項資料表和子項資料表的所選欄所組成。 查詢實體時,您必須根據該實體的可用資料欄提供投影和搜尋條件。結果會產生 Cursor,其中每個擷取的子項資料表資料列都包含一個資料列。舉例來說,如果您查詢 ContactsContract.Contacts.Entity 的聯絡人名稱,以及該名稱的所有原始聯絡人的所有 ContactsContract.CommonDataKinds.Email 列,您會收到一個 Cursor,其中包含每個 ContactsContract.CommonDataKinds.Email 列的一列。

實體簡化查詢作業。使用實體即可一次擷取聯絡人或原始聯絡人的所有聯絡人資料,而不必先查詢父項資料表來取得 ID,然後再透過該 ID 查詢子項資料表。此外,通訊錄提供者會在單一交易中處理針對實體的查詢,確保擷取的資料在內部一致。

注意:實體通常不會包含父項和子項資料表的所有欄。如果您嘗試使用不在實體的欄名常數清單中的欄名,系統會傳回 Exception

以下程式碼片段說明如何擷取聯絡人的所有原始聯絡人資料列。這個程式碼片段屬於大型應用程式,其中包含「main」和「detail」兩個活動。主要活動會顯示聯絡人資料列清單;當使用者選取其中一個時,活動會將其 ID 傳送至詳細資料活動。詳細資料活動會使用 ContactsContract.Contacts.Entity,顯示與所選聯絡人相關聯的所有原始聯絡人中的所有資料列。

以下程式碼片段取自「detail」活動:

Kotlin

...
    /*
     * 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).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.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
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * 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.
     */
    val projection: Array<String> = arrayOf(
            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.
     */
    val 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 CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // 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.
    )
}

Java

...
    /*
     * 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).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            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
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> 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
            contactUri,              // 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() 中的所有作業,因此您所做的修改絕不會讓聯絡人存放區處於不一致的狀態。批次修改功能還可同時插入原始聯絡人及其詳細資料。

注意:如要修改「單一」原始聯絡人,建議傳送意圖至裝置的聯絡人應用程式,不要處理應用程式中的修改內容。詳情請參閱「使用意圖擷取及修改」一節。

收益點

包含大量作業的批次修改作業可能會阻斷其他程序,導致整體使用者體驗不佳。如要將要執行的所有修改項目整理到盡可能少的個別清單中,並同時避免這些項目阻斷系統,您應為一或多項作業設定產生點。收益點是 ContentProviderOperation 物件,其 isYieldAllowed() 值設為 true。當聯絡人供應器遇到產生點時,會暫停工作,讓其他程序執行並關閉目前的交易。供應器重新啟動時,會繼續執行 ArrayList 中的下一個作業,並開始新的交易。

每個 applyBatch() 呼叫都會產生多筆交易,因此,您應為一組相關資料列的最後一項作業設定產生點。舉例來說,您應該為加入原始聯絡資料列及其相關資料列的集合,或與單一聯絡人相關的資料列集合,設定最後一項作業的產生點。

產生點也是原子作業的單位。兩個產生點之間的所有存取作業都會以單一單位的形式成功或失敗。如果您未設定任何產生點,最小的原子作業就是整個批次作業。如果您使用交出點,就能避免作業降低系統效能,同時確保部分作業是原子作業。

修改回參

當您要插入新的原始聯絡人資料列及其相關聯的資料列做為一組 ContentProviderOperation 物件時,您必須將原始聯絡人的 _ID 值插入做為 RAW_CONTACT_ID 值,以便將資料列連結至原始聯絡人資料列。不過,如果您為資料列建立 ContentProviderOperation,就無法使用這個值,因為您尚未為原始聯絡人列套用 ContentProviderOperation。如要解決這個問題,ContentProviderOperation.Builder 類別具有 withValueBackReference() 方法。這個方法可讓您使用先前作業的結果插入或修改資料欄。

withValueBackReference() 方法有兩個引數:

key
鍵/值組合的鍵。這個引數的值應為您要修改的資料表中資料欄的名稱。
previousResult
applyBatch()ContentProviderResult 物件陣列中值的 0 起算索引。套用批次作業時,每項作業的結果會儲存在中繼結果陣列中。previousResult 值是這些結果的索引,其透過 key 值擷取並儲存。這樣一來,您就能插入新的原始聯絡人記錄並取得其 _ID 值,然後在新增 ContactsContract.Data 列時對該值建立「回溯參照」。

您首次呼叫 applyBatch() 時會建立整個結果陣列,其大小等於您提供的 ContentProviderOperation 物件 ArrayList 的大小。不過,結果陣列中的所有元素都會設為 null,如果您嘗試對尚未套用的作業進行結果回溯參照,withValueBackReference() 就會擲回 Exception

下列程式碼片段說明如何批次插入新的原始聯絡人和資料。其中包含建立產生點並使用回溯參照的程式碼。

第一個程式碼片段會從使用者介面擷取聯絡人資料。此時使用者已選取要新增原始聯絡人的帳戶。

Kotlin

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

下列程式碼片段會建立作業,將原始聯絡資料列插入 ContactsContract.RawContacts 資料表:

Kotlin

    /*
     * 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.
    val ops = arrayListOf<ContentProviderOperation>()

    /*
     * 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.
     */
    var op: ContentProviderOperation.Builder =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    /*
     * 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<ContentProviderOperation> ops =
            new ArrayList<ContentProviderOperation>();

    /*
     * 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, selectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

接下來,程式碼會為顯示名稱、電話和電子郵件地址建立資料列。

每個作業建構工具物件都會使用 withValueBackReference() 取得 RAW_CONTACT_ID。參照點會回溯至第一個作業的 ContentProviderResult 物件,該作業會新增原始聯絡人資料列,並傳回新的 _ID 值。因此,每個資料列都會透過其 RAW_CONTACT_ID 自動連結至所屬的新 ContactsContract.RawContacts 資料列。

新增電子郵件資料列的 ContentProviderOperation.Builder 物件會標示為 withYieldAllowed(),用於設定產生點:

Kotlin

    // 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())

Java

    // 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() 的呼叫,該呼叫會插入新的原始聯絡人和資料列。

Kotlin

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Java

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.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 列時使用最佳化並行控制項,請按照下列步驟操作:

  1. 擷取原始聯絡人的 VERSION 欄,以及您擷取的其他資料。
  2. 使用 newAssertQuery(Uri) 方法建立適合強制限制的 ContentProviderOperation.Builder 物件。針對內容 URI,請使用 RawContacts.CONTENT_URI 搭配附加原始聯絡人的 _ID
  3. 針對 ContentProviderOperation.Builder 物件,請呼叫 withValue(),將 VERSION 欄與您剛擷取的版本號碼進行比較。
  4. 針對相同的 ContentProviderOperation.Builder,請呼叫 withExpectedCount(),確保這項斷言只測試一個資料列。
  5. 呼叫 build() 建立 ContentProviderOperation 物件,然後將此物件新增為傳遞至 applyBatch()ArrayList 中的第一個物件。
  6. 套用批次交易。

如果在您讀取資料列與嘗試修改資料列之間,有其他作業更新原始聯絡資料列,則「斷言」ContentProviderOperation 會失敗,整個批次作業也會撤銷。接著,您可以選擇重試批次或採取其他動作。

下列程式碼片段示範如何使用 CursorLoader 查詢單一原始聯絡人,然後建立「assert」ContentProviderOperation

Kotlin

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID))
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION))
}

...

// Sets up a Uri for the assert operation
val rawContactUri: Uri = ContentUris.withAppendedId(
        ContactsContract.RawContacts.CONTENT_URI,
        rawContactID
)

// Creates a builder for the assert operation
val assertOp: ContentProviderOperation.Builder =
        ContentProviderOperation.newAssertQuery(rawContactUri).apply {
            // Adds the assertions to the assert operation: checks the version
            withValue(SyncColumns.VERSION, mVersion)

            // and count of rows tested
            withExpectedCount(1)
        }

// Creates an ArrayList to hold the ContentProviderOperation objects
val ops = arrayListOf<ContentProviderOperation>()

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 {
    val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops)
} catch (e: OperationApplicationException) {
    // Actions you want to take if the assert operation fails go here
}

Java

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    rawContactID = 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, rawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(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<ContentProviderOperation>;

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
    }

使用意圖擷取及修改

傳送意圖至裝置的聯絡人應用程式,即可間接存取聯絡人提供者。意圖會啟動裝置的聯絡人應用程式 UI,讓使用者執行聯絡人相關工作。使用者可以透過這類存取權執行下列操作:

  • 從清單中挑選聯絡人,然後將其傳回您的應用程式,進行後續作業。
  • 編輯現有聯絡人的資料。
  • 為任何帳戶插入新的原始聯絡人。
  • 刪除聯絡人或聯絡人資料。

如果使用者要插入或更新資料,您可以先收集資料,然後將資料傳送為意圖的一部分。

當您使用意圖透過裝置的聯絡人應用程式存取聯絡人提供者時,不必自行編寫 UI 或程式碼來存取提供者。您也不需要向供應器申請讀取或寫入權限。裝置的聯絡人應用程式可以將聯絡人的讀取權限委派給您,而且由於您是透過其他應用程式修改提供者,因此不必具備寫入權限。

如要詳細瞭解傳送意圖存取供應器的一般程序,請參閱「 內容供應器基本概念」指南中的「透過意圖存取資料」一節。表 4 列出您可用於可用工作項的動作、MIME 類型和資料值,而 ContactsContract.Intents.Insert 參考文件則列出可與 putExtra() 搭配使用的額外值:

表 4. 聯絡人供應程式意圖。

工作 動作 資料 MIME 類型 附註
從清單中選取聯絡人 ACTION_PICK 以下其中之一: 未使用 視您提供的內容 URI 類型而定,顯示原始聯絡人清單或原始聯絡人的資料清單。

呼叫 startActivityForResult(),傳回所選資料列的內容 URI。URI 的格式是資料表的內容 URI,並附加資料列的 LOOKUP_ID。裝置的聯絡人應用程式會將讀取和寫入權限委派給這個內容 URI,直到活動結束為止。詳情請參閱 內容供應者基本資訊指南。

插入新的原始聯絡資訊 Insert.ACTION RawContacts.CONTENT_TYPE,MIME 類型。 顯示裝置聯絡人應用程式的「新增聯絡人」畫面。系統會顯示您新增至意圖的額外值。如果與 startActivityForResult() 一併傳送,新增的原始聯絡人內容 URI 會回傳至「data」欄位中活動在 Intent 引數中的 onActivityResult() 回呼方法。如要取得值,請呼叫 getData()
編輯聯絡人 ACTION_EDIT 聯絡人為 CONTENT_LOOKUP_URI。編輯器活動可讓使用者編輯與此聯絡人相關聯的任何資料。 Contacts.CONTENT_ITEM_TYPE,單一聯絡人。 在聯絡人應用程式中顯示「編輯聯絡人」畫面。系統會顯示您新增至意圖的額外值。當使用者按一下「Done」儲存編輯內容時,活動就會返回前景。
顯示可新增資料的挑選器。 ACTION_INSERT_OR_EDIT CONTENT_ITEM_TYPE 這項意圖一律會顯示聯絡人應用程式的挑選器畫面。使用者可以選擇要編輯的聯絡人,或新增聯絡人。系統會根據使用者的選擇顯示編輯或新增畫面,並顯示您在意圖中傳遞的額外資料。如果您的應用程式會顯示電子郵件或電話號碼等聯絡人資料,請使用這個意圖,讓使用者將資料新增至現有的聯絡人。聯絡人

注意:您不需要在這項意圖的額外項目中傳送名稱值,因為使用者一律會選擇現有名稱或新增名稱。此外,如果您傳送名稱,而使用者選擇進行編輯,聯絡人應用程式將會顯示您傳送的名稱,並覆寫先前的值。如果使用者沒有注意到這點,並儲存編輯內容,舊值就會遺失。

裝置的聯絡人應用程式不允許您使用意圖刪除原始聯絡人或其任何資料。如要刪除原始聯絡人,請改用 ContentResolver.delete()ContentProviderOperation.newDelete()

下列程式碼片段說明如何建構並傳送插入新原始聯絡人和資料的意圖:

Kotlin

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * 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
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    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
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * 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
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Java

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.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<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * 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, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.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.CommonDataKinds.StructuredName 列的 ContactsContract.RawContacts 資料列,可能會導致匯總期間發生問題。
請一律將新的 ContactsContract.Data 資料列連結至其父項 ContactsContract.RawContacts 資料列。
未連結至 ContactsContract.RawContactsContactsContract.Data 列將不會顯示在裝置的聯絡人應用程式中,而且可能導致同步轉接器相關問題。
只變更您擁有的原始聯絡人資料。
請注意,聯絡人供應商通常會管理多種不同帳戶類型/線上服務的資料。您必須確保應用程式只修改或刪除屬於您資料列的資料,且僅以您控管的帳戶類型和名稱插入資料。
請一律使用 ContactsContract 和其子類別中定義的常數,用於授權、內容 URI、URI 路徑、資料欄名稱、MIME 類型和 TYPE 值。
使用這些常數有助於避免錯誤。如果任何常數已淘汰,您也會收到編譯器警告通知。

自訂資料列

您可以建立及使用自訂 MIME 類型,在 ContactsContract.Data 資料表中插入、編輯、刪除及擷取自有資料列。您只能使用 ContactsContract.DataColumns 中定義的資料欄,但可以將自訂類型專屬的資料欄名稱對應至預設資料欄名稱。在裝置的聯絡人應用程式中,系統會顯示資料列的資料,但無法編輯或刪除,使用者也無法新增其他資料。如要允許使用者修改自訂資料列,您必須在自己的應用程式中提供編輯器活動。

如要顯示自訂資料,請提供包含 <ContactsAccountType> 元素和一或多個 <ContactsDataKind> 子元素的 contacts.xml 檔案。詳情請參閱 <ContactsDataKind> element 一節。

如要進一步瞭解自訂 MIME 類型,請參閱 建立內容供應器指南。

聯絡人供應程式同步轉換器

聯絡人提供者專門用於處理裝置與線上服務之間的聯絡人資料同步處理。這樣做可讓使用者將現有資料下載至新裝置,並將現有資料上傳至新帳戶。同步處理功能還可確保使用者隨時都能取得最新資料,無論新增和變更的來源為何。同步處理的另一個優點是,即使裝置未連上網路,也能使用聯絡人資料。

雖然您可以透過多種方式實作同步處理,但 Android 系統提供外掛程式同步處理架構,可自動執行下列工作:

  • 檢查網路可用性。
  • 根據使用者的偏好設定安排及執行同步處理作業。
  • 重新啟動已停止的同步處理作業。

如要使用這個架構,請提供同步轉換介面外掛程式。每個同步處理器轉接器都會與服務和內容供應器一一對應,但可處理相同服務的多個帳戶名稱。這個架構也允許同一服務和供應商使用多個同步轉換介面。

同步轉換器類別和檔案

將同步處理轉接程式實作為 AbstractThreadedSyncAdapter 的子類別,然後將其安裝為 Android 應用程式的一部分。系統會從應用程式資訊清單中的元素,以及資訊清單所指向的特殊 XML 檔案,瞭解同步處理器的相關資訊。XML 檔案會定義線上服務的帳戶類型和內容供應者的權限,這兩者共同可用於唯一識別轉接器。使用者必須新增同步處理轉接器的帳戶類型,並為同步處理轉接器同步的內容提供者啟用同步處理功能,同步處理轉接器才會啟用。此時,系統會開始管理轉接器,並視需要呼叫轉接器,以便在內容供應器和伺服器之間進行同步。

注意:使用帳戶類型做為同步處理器的識別資訊,可讓系統偵測並將同一機構中存取不同服務的同步處理器分組。舉例來說,Google 線上服務的同步轉接器都具有相同的帳戶類型 com.google。使用者在裝置上新增 Google 帳戶時,系統會一併列出所有已安裝的 Google 服務同步處理器,每個列出的同步處理器都會與裝置上的不同內容供應器同步。

由於大多數服務都要求使用者先驗證身分才能存取資料,Android 系統會提供驗證架構,這類似於同步處理的對應程式庫架構,且通常會與對應程式庫架構搭配使用。驗證架構會使用外掛程式驗證器,這些驗證器是 AbstractAccountAuthenticator 的子類別。驗證器會按照下列步驟驗證使用者身分:

  1. 收集使用者的名稱、密碼或類似資訊 (使用者的憑證)。
  2. 將憑證傳送至服務
  3. 檢查服務的回應。

如果服務接受憑證,驗證工具就能儲存憑證,供日後使用。由於有外掛程式驗證器架構,AccountManager 可以提供驗證器支援且選擇公開的任何驗證權杖存取權,例如 OAuth2 驗證權杖。

雖然驗證並非必要,但大多數的聯絡人服務都會使用驗證。不過,您不一定要使用 Android 驗證架構來進行驗證程序。

同步轉換介面實作

如要實作聯絡人供應程式的同步轉換介面,請先建立含有以下項目的 Android 應用程式:

Service 元件會回應系統要求,以便繫結至同步處理器。
系統想要執行同步處理時,會呼叫服務的 onBind() 方法,取得同步轉換介面的 IBinder。這麼做可讓系統對轉接程式的方法進行跨程序呼叫。
實際的同步轉換介面,實作為 AbstractThreadedSyncAdapter 的具體子類別。
This 類別會執行從伺服器下載資料、從裝置上傳資料,以及解決衝突的工作。轉接器的主要工作是在 onPerformSync() 方法中完成。這個類別必須以單例模式例項化。
Application 的子類別。
This class acts as a factory for the sync adapter singleton. 使用 onCreate() 方法將同步處理器實例化,並提供靜態「getter」方法,將單例傳回至同步處理器服務的 onBind() 方法。
選用:用於回應系統要求的 Service 元件,以便驗證使用者。
AccountManager 會啟動這項服務,開始驗證程序。服務的 onCreate() 方法會將驗證器物件例項化。當系統想要針對應用程式的同步轉換介面驗證使用者帳戶時,會呼叫服務的 onBind() 方法,取得驗證器的 IBinder。這可讓系統對驗證器的方法進行跨程序呼叫。
選用: 具體的 AbstractAccountAuthenticator 子類別,用於處理驗證要求。
This 類別提供 AccountManager 叫用的驗證方法,可透過伺服器驗證使用者的憑證。驗證程序的詳細資料會因所使用的伺服器技術而有很大差異。請參閱伺服器軟體的說明文件,進一步瞭解驗證機制。
定義系統同步處理轉接器和驗證器的 XML 檔案。
上述同步處理轉接程式和驗證器服務元件,是在應用程式資訊清單的 <service> 元素中定義。這些元素包含 <meta-data> 子元素,可為系統提供特定資料:
  • 同步處理器轉接程式服務的 <meta-data> 元素會指向 XML 檔案 res/xml/syncadapter.xml。這個檔案會依序指定網路服務的 URI (將與聯絡人供應器同步),以及網路服務的帳戶類型。
  • 選用:驗證工具的 <meta-data> 元素會指向 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
必要。與此串流項目相關聯的原始聯絡人的使用者帳戶名稱。插入串流項目時,請務必設定這個值。
ID 欄
必要。插入串流項目時,必須插入下列 ID 欄:
  • 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.StreamItemsColumn#RAW_CONTACT_ID:與這個串流項目相關聯的原始聯絡人的 android.provider.BaseColumn#_ID 值。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
選用。儲存可在串流項目開頭顯示的摘要資訊。
android.provider.ContactsContract.StreamItemsColumn#TEXT
串流項目的文字,可能是項目來源發布的內容,或是產生串流項目的某些動作說明。這個欄可包含任何格式和嵌入的資源圖片,這些圖片可由 fromHtml() 算繪。供應器可能會截斷或省略長內容,但會盡量避免破壞標記。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
文字字串,其中包含插入或更新串流項目的時間,以自紀元起算的毫秒為單位。插入或更新串流項目的應用程式負責維護這個資料欄,Contacts 供應器不會自動維護這個資料欄。

如要顯示串流項目的識別資訊,請使用 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 資料表的 _ID 欄值。相片參考資料會儲存在資料表的以下欄中:

android.provider.ContactsContract.StreamItemPhotos#PHOTO 欄 (BLOB)。
相片的二進位表示法,由供應商調整大小,以利儲存和顯示。這個資料欄可與舊版聯絡人提供者回溯相容,舊版提供者會將此欄用於儲存相片。不過,在目前的版本中,您不應使用這個資料欄來儲存相片。請改用 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID 或 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (兩者皆在下文中說明),將相片儲存在檔案中。這個資料欄現在包含可供讀取的相片縮圖。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
原始聯絡人相片的數字 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 資料表,每個原始聯絡人的資料列數量皆有限制。一旦達到這個上限,Contacts 提供者會自動刪除最舊的 android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP 資料列,騰出空間給新的串流項目資料列。如要取得此限制,請對內容 URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI 執行查詢。您可以將內容 URI 設為 null 以外的所有引數保留下來。這項查詢會傳回包含單一資料列的游標,其中包含單一資料欄 android.provider.ContactsContract.StreamItems#MAX_ITEMS。

android.provider.ContactsContract.StreamItems.StreamItemPhotos 類別會定義 android.provider.ContactsContract.StreamItemPhotos 的子表格,其中包含單一串流項目的相片列。

社群串流互動

聯絡人供應器管理的社群串流資料,搭配裝置的聯絡人應用程式,可提供強大的功能,將您的社群網路系統與現有聯絡人連結。可用的功能如下:

  • 您可以使用同步處理轉接器,將社交網路服務同步至聯絡人提供者,藉此擷取使用者聯絡人的近期活動,並儲存在 android.provider.ContactsContract.StreamItems 和 android.provider.ContactsContract.StreamItemPhotos 資料表中,以供日後使用。
  • 除了定期同步作業之外,您還可以觸發同步處理器,在使用者選取要查看的聯絡人時擷取其他資料。這樣一來,同步處理的適配器就能擷取高解析度相片,以及聯絡人的最新串流項目。
  • 您可以透過裝置的聯絡人應用程式和聯絡人提供者註冊通知,在查看聯絡人時接收意圖,並在該時刻透過服務更新聯絡人的狀態。與使用同步轉接程式執行完整同步處理相比,這種做法可能速度更快,所需頻寬也較少。
  • 使用者可以在裝置的聯絡人應用程式中查看聯絡人時,將聯絡人新增至您的社群網路服務。您可以透過「邀請聯絡人」功能啟用這項功能,方法是將活動 (可將現有聯絡人新增至網路) 和 XML 檔案 (可為裝置的聯絡人應用程式和聯絡人提供者提供應用程式詳細資料) 結合使用。

與聯絡人提供者對串流項目進行一般同步處理的方式,與其他同步處理作業相同。如要進一步瞭解同步處理,請參閱「聯絡資訊供應器同步處理轉接器」一節。接下來的兩節將說明註冊通知和邀請聯絡人的方式。

註冊以處理社交網路觀看次數

如要註冊同步處理套件,以便在使用者查看由同步處理套件管理的聯絡人時收到通知,請按照下列步驟操作:

  1. 在專案的 res/xml/ 目錄中建立名為 contacts.xml 的檔案。如果您已擁有這個檔案,可以略過這個步驟。
  2. 在這個檔案中,新增 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。如果此元素已存在,您可以略過這個步驟。
  3. 如要註冊服務,以便在使用者在裝置的聯絡資料應用程式中開啟聯絡人的詳細資料頁面時收到通知,請將屬性 viewContactNotifyService="serviceclass" 新增至元素,其中 serviceclass 是服務的完全限定類別名稱,應從裝置的聯絡資料應用程式接收意圖。針對通知器服務,請使用擴充 IntentService 的類別,讓服務接收意圖。傳入意圖中的資料含有使用者點選的原始聯絡人內容 URI。您可以從通知器服務繫結並呼叫同步處理的適配器,以便更新原始聯絡人的資料。

如要註冊活動,以便在使用者點選串流項目或相片 (或兩者皆有) 時呼叫,請按照下列步驟操作:

  1. 在專案的 res/xml/ 目錄中建立名為 contacts.xml 的檔案。如果您已擁有此檔案,可以略過這個步驟。
  2. 在這個檔案中,新增 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。如果此元素已存在,您可以略過這個步驟。
  3. 如要註冊其中一個活動,以便處理使用者在裝置聯絡應用程式中按一下串流項目的情況,請將 viewStreamItemActivity="activityclass" 屬性新增至元素,其中 activityclass 是活動的完全修飾類別名稱,該活動應會從裝置聯絡應用程式接收意圖。
  4. 如要註冊活動來處理使用者在裝置聯絡應用程式中按一下串流相片的情況,請將 viewStreamItemPhotoActivity="activityclass" 屬性新增至元素,其中 activityclass 是活動的完全修飾類別名稱,該活動應會從裝置聯絡應用程式接收意圖。

如要進一步瞭解 <ContactsAccountType> 元素,請參閱 <ContactsAccountType> 元素一節。

傳入的意圖包含使用者按一下的項目或相片內容 URI。如要為文字項目和相片建立個別活動,請在同一個檔案中使用這兩種屬性。

與社群網路服務互動

使用者不必離開裝置的聯絡人應用程式,即可邀請聯絡人加入您的社群網站。您可以改為讓裝置的聯絡人應用程式傳送意圖,邀請對方邀請聯絡人參與您的其中一項活動。如要設定這項功能,請按照下列步驟操作:

  1. 在專案的 res/xml/ 目錄中建立名為 contacts.xml 的檔案。如果您已經擁有這個檔案,可以略過這個步驟。
  2. 在這個檔案中,新增 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。如果此元素已存在,您可以略過這個步驟。
  3. 新增下列屬性:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    activityclass 值是應接收意圖的活動的完整類別名稱。invite_action_label 值是文字字串,會顯示在裝置聯絡人應用程式的「Add Connection」選單中。

注意: ContactsSourceContactsAccountType 的已淘汰代碼名稱。

contacts.xml 參考資料

contacts.xml 檔案包含 XML 元素,可控制同步轉換器和應用程式與聯絡人應用程式和聯絡人供應器的互動情形。以下各節將說明這些元素。

<ContactsAccountType> 元素

<ContactsAccountType> 元素可控制應用程式與聯絡人應用程式的互動方式。語法如下:

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

包含在:

res/xml/contacts.xml

可包含:

<ContactsDataKind>

說明:

宣告 Android 元件和 UI 標籤,讓使用者邀請聯絡人加入社群網路、在社群網路串流更新時通知使用者,等等。

請注意,<ContactsAccountType> 的屬性不需要加上屬性前置字串 android:

屬性:

inviteContactActivity
應用程式中活動的完整類別名稱,當使用者從裝置的聯絡人應用程式中選取「Add connection」時,您要啟動的活動。
inviteContactActionLabel
系統會在 inviteContactActivity 的「新增連線」選單中,為指定活動顯示的文字字串。例如「追蹤我的網路」字串時,您可以使用字串資源 ID 做為這個標籤。
viewContactNotifyService
應用程式中服務的完整類別名稱,應在使用者查看聯絡人時收到通知。這項通知是由裝置的聯絡人應用程式傳送,可讓應用程式將需要大量資料的作業延後執行,直到需要時再執行。舉例來說,應用程式可以讀取並顯示聯絡人的高解析度相片和最新的社群媒體動態項目,以回應這項通知。如需進一步瞭解這項功能,請參閱「社群串流互動」一節。
viewGroupActivity
應用程式中可顯示群組資訊的活動完整類別名稱。當使用者在裝置的聯絡人應用程式中點選群組標籤時,系統就會顯示該活動的 UI。
viewGroupActionLabel
聯絡人應用程式顯示的 UI 控制項標籤,可讓使用者查看應用程式中的群組。

此屬性允許使用字串資源 ID。

viewStreamItemActivity
應用程式中活動的完整類別名稱,當使用者點選原始聯絡人的串流項目時,裝置的聯絡人應用程式會啟動該活動。
viewStreamItemPhotoActivity
當使用者在原始聯絡人中點選串流項目中的相片時,系統會啟動應用程式中活動的完整類別名稱。

<ContactsDataKind> 元素

<ContactsDataKind> 元素會控制應用程式在聯絡人應用程式的 UI 中顯示自訂資料列的方式。其語法如下:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

包含在:

<ContactsAccountType>

說明:

使用這個元素,讓聯絡人應用程式將自訂資料列的內容,顯示為原始聯絡人的詳細資料的一部分。<ContactsAccountType> 的每個 <ContactsDataKind> 子元素都代表一種自訂資料列類型,同步處理轉接器會將這類型新增至 ContactsContract.Data 資料表。針對每個您使用的自訂 MIME 類型,新增一個 <ContactsDataKind> 元素。如果不想新增元素,則無須新增元素。

屬性:

android:mimeType
您為 ContactsContract.Data 資料表中的其中一個自訂資料列類型定義的自訂 MIME 類型。舉例來說,vnd.android.cursor.item/vnd.example.locationstatus 值可以是資料列的 MIME 類型自訂值,用於記錄聯絡人的最後已知位置。
android:icon
聯絡人應用程式會在資料旁顯示的 Android 可繪製資源。您可以使用這項功能向使用者指出資料來自您的服務。
android:summaryColumn
從資料列擷取到的兩個值中,前兩個值的資料欄名稱。這個值會顯示為這個資料列項目的第一行。第一行是用來做為資料摘要,但這不是必要的。另請參閱 android:detailColumn
android:detailColumn
從資料列擷取的兩個值中第二個值的資料欄名稱。這個值會顯示為這個資料列的項目第二行。另請參閱 android:summaryColumn

其他聯絡人提供者功能

除了前面幾節所述的主要功能之外,聯絡人提供者還提供以下實用功能,可用於處理聯絡人資料:

  • 聯絡人群組
  • 相片功能

聯絡人群組

聯絡人供應器可以選擇使用群組資料標記相關聯絡人的集合。如果與使用者帳戶相關聯的伺服器想要維護群組,則帳戶類型的同步處理器應在聯絡資訊提供者和伺服器之間傳輸群組資料。當使用者將新聯絡人新增至伺服器,然後將該聯絡人放入新群組時,同步處理的適配器必須將新群組新增至 ContactsContract.Groups 資料表。原始聯絡人所屬的群組或群組,會使用 ContactsContract.CommonDataKinds.GroupMembership MIME 類型儲存在 ContactsContract.Data 資料表中。

如果您設計的同步處理轉接器會將伺服器中的原始聯絡資料新增至聯絡資訊提供者,且您未使用群組,則必須告知提供者讓資料顯示。在使用者新增帳戶至裝置時執行的程式碼中,更新聯絡人提供者為帳戶新增的 ContactsContract.Settings 列。在此列中,將 Settings.UNGROUPED_VISIBLE 欄的值設為 1。這樣一來,即使您未使用群組,聯絡資訊供應器也會一律顯示您的聯絡人資料。

聯絡人相片

ContactsContract.Data 資料表會以 MIME 類型 Photo.CONTENT_ITEM_TYPE 的形式,將相片儲存為資料列。資料列的 CONTACT_ID 欄會連結至所屬原始聯絡人的 _ID 欄。ContactsContract.Contacts.Photo 類別定義了 ContactsContract.Contacts 的子表格,其中包含聯絡人主要相片的相片資訊,是聯絡人主要原始聯絡人的主要相片。同樣地,ContactsContract.RawContacts.DisplayPhoto 類別會定義 ContactsContract.RawContacts 的子資料表,其中包含原始聯絡人的主相片相片資訊。

ContactsContract.Contacts.PhotoContactsContract.RawContacts.DisplayPhoto 的參考文件包含擷取相片資訊的範例。擷取原始聯絡人的主要縮圖並沒有便利類別,但您可以傳送查詢至 ContactsContract.Data 資料表,選取原始聯絡人的 _IDPhoto.CONTENT_ITEM_TYPEIS_PRIMARY 欄,找出原始聯絡人的主要相片列。

使用者社交動態串流資料也可能包含相片。這些資料會儲存在 android.provider.ContactsContract.StreamItemPhotos 資料表,如要進一步瞭解,請參閱「社交串流相片」一節。