聯絡人供應程式

聯絡人供應程式是功能強大且靈活的 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。請務必使用自有或控管網域的網域 ID,證明帳戶類型。這樣一來,您的帳戶類型就不會重複。 提供聯絡人資料的帳戶類型通常會有相關聯的同步轉接器,以便與聯絡人供應商同步處理。
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),應用程式才能新增原始聯絡人列。您可以在說明文件中向使用者說明這項規定,或提示使用者新增類型和名稱 (或兩者並用)。下一節將進一步說明帳戶類型和帳戶名稱。

原始聯絡人資料的來源

如要瞭解原始聯絡人的運作方式,假設「Emily Dickinson」在她的裝置上定義了下列三個使用者帳戶:

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

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

假設 Emily Dickinson 開啟瀏覽器視窗,以 emily.dickinson@gmail.com 登入 Gmail,開啟 Google 聯絡人,然後新增「Thomas Higginson」。之後她以 emilyd@gmail.com 的身分登入 Gmail,並傳送電子郵件至「Thomas Higginson」,讓系統自動將他新增為聯絡人。她也追蹤了 Twitter 上的「popel_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。

資料

如前文所述,原始聯絡人的資料會儲存在 ContactsContract.Data 列中,該資料列已連結至原始聯絡人的 _ID 值。如此一來,單一原始聯絡人就會擁有多個相同類型的資料執行個體,例如電子郵件地址或電話號碼。舉例來說,如果 emilyd@gmail.com 的「Thomas Higginson」(與 Google 帳戶 emilyd@gmail.com 相關聯的 Thomas Higginson 原始聯絡列) 的住家電子郵件地址 和公司電子郵件地址為 thomas.higginson@gmail.com,則聯絡人提供者會儲存兩個電子郵件地址列,並將這兩個電子郵件地址列連結至原始聯絡人。 thigg@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) 資料,例如相片縮圖。

特定類型資料欄名稱

為協助您使用特定資料列類型的資料欄,聯絡人供應程式也提供特定類型資料欄名稱常數 (在 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.RawContacts 的「CONTACT_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 顯示 Contacts Provider 如何融入人類資料流。在標示「同步轉換介面」的方塊中,每個轉接器都會依帳戶類型標示。

人事資料流

圖 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 應用程式更新資料列時,聯絡人供應程式會自動設定這個值。

如果同步轉換介面會修改原始聯絡人或資料表,應一律將 CALLER_IS_SYNCADAPTER 字串附加至其使用的內容 URI。如此可避免供應器將資料列標示為無效。否則,即使伺服器是修改來源,系統仍會將同步轉接程式修改內容顯示為本機修改並傳送至伺服器。

「1」:自上次同步處理後變更過,必須同步到伺服器。
ContactsContract.RawContacts VERSION 此資料列的版本號碼。 聯絡人提供者每列或其相關資料變更時,就會自動遞增這個值。
ContactsContract.Data DATA_VERSION 此資料列的版本號碼。 每當資料列有所變更,聯絡人供應程式就會自動遞增這個值。
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 來顯示與所選聯絡人相關聯的所有原始聯絡人的所有資料列。

這段程式碼取自「詳細資料」活動:

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 查詢單一原始聯絡人後,建立「斷言」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 傳回至 Intent 引數 (位於「data」欄位) 中活動的 onActivityResult() 回呼方法。如要取得值,請呼叫 getData()
編輯聯絡人 ACTION_EDIT CONTENT_LOOKUP_URI。透過編輯器活動,使用者可以編輯與這位聯絡人相關聯的任何資料。 Contacts.CONTENT_ITEM_TYPE,單一聯絡人。 在「聯絡人」應用程式中顯示「編輯聯絡人」畫面。系統隨即會顯示您在意圖中新增的額外值。使用者按一下「完成」儲存編輯內容後,活動會返回前景。
顯示也可以新增資料的挑選器。 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.RawContacts 資料列在 ContactsContract.Data 資料表中沒有 ContactsContract.CommonDataKinds.StructuredName 列,則匯總期間可能會導致發生問題。
一律將新的 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 的具體子類別實作。
這個類別會負責從伺服器下載資料、從裝置上傳資料,並解決衝突問題。轉接程式的主要工作是在方法 onPerformSync() 中完成。此類別必須執行個體化為單例模式。
Application 的子類別。
這個類別可做為同步轉接器單例的工廠。使用 onCreate() 方法將同步轉接器例項化,並提供靜態的「getter」方法,將單例模式傳回同步處理轉接器服務的 onBind() 方法。
選用:Service 元件,用於回應系統發出的使用者驗證要求。
AccountManager 會啟動這項服務來展開驗證程序。服務的 onCreate() 方法會將驗證器物件例項化。系統要驗證應用程式的同步轉換介面使用者帳戶時,會呼叫服務的 onBind() 方法,以取得驗證器的 IBinder。這可讓系統對驗證器的方法進行跨程序呼叫。
選用:處理驗證要求的 AbstractAccountAuthenticator 具體子類別。
這個類別會提供 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.StreamItemsColumns#RAW_CONTACT_ID:與這個串流項目相關聯的原始聯絡人 android.provider.BaseColumns#_ID 值。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
選用元素。儲存摘要資訊,此資訊可在串流項目的開頭顯示。
android.provider.ContactsContract.StreamItemsColumns#TEXT
串流項目的文字,可以是項目來源張貼的內容,或是產生串流項目的部分動作說明。此欄可包含任何可由 fromHtml() 轉譯的格式設定和內嵌資源圖片。供應器可能會截斷或省略長篇內容,但會盡量避免中斷標記。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
文字字串,包含插入或更新串流項目的時間,以 Epoch 紀元時間起算的毫秒格式表示。插入或更新串流項目的應用程式須負責維護這個資料欄;聯絡人供應程式不會自動維護這個資料欄。

如要顯示串流項目的識別資訊,請使用 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 資料表,儲存每位原始聯絡人的資料列數有限。達到這個上限後,Contact Provider 會自動刪除含有最舊 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 子表格,包含單一串流項目的相片列。

社交訊息串互動

由聯絡人供應程式管理的社交訊息串資料,搭配裝置的聯絡人應用程式,可提供有效連結您的社群網路系統與現有聯絡人的強大方法。以下為可用的地圖項目:

  • 只要使用同步轉換器,將社群網路服務與聯絡人供應商同步處理,即可擷取使用者的聯絡人最近活動,並儲存至 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 的已淘汰標記名稱。

contact.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
當使用者從裝置的聯絡人應用程式中選取「新增連線」時,您希望在應用程式中啟用的活動完整類別名稱。
inviteContactActionLabel
在「新增連線」選單中,針對 inviteContactActivity 中指定的活動顯示的文字字串。舉例來說,您可以使用「追蹤我的網路」字串。您可以使用這個標籤的字串資源 ID。
viewContactNotifyService
應用程式中服務的完整類別名稱,當使用者查看聯絡人時,應接收通知。此通知是由裝置的聯絡人應用程式傳送,可讓應用程式將需要大量資料的作業延後到需要時再執行。舉例來說,您的應用程式可以讀取並顯示聯絡人的高解析度相片以及最新的社交訊息串項目,藉此回應這則通知。如要進一步瞭解這項功能,請參閱「社交串流互動」一節。
viewGroupActivity
應用程式中活動的完整類別名稱,可顯示群組資訊。當使用者在裝置的聯絡人應用程式中按一下群組標籤時,系統會顯示此活動的 UI。
viewGroupActionLabel
聯絡人應用程式為使用者介面控制項顯示的標籤,可讓使用者查看應用程式中的群組。

這個屬性可以使用字串資源 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 表格中,詳情請參閱「社交串流相片」一節。