联系人提供程序

联系人提供程序是一个强大且灵活的 Android 组件,用于管理 存储用户相关数据的中央存储库。联系人提供程序是数据的来源 您在设备的联系人应用中看到过该应用,还可以在自己的手机中访问该应用的数据 并在设备和在线服务之间传输数据。该提供商提供 各种各样的数据源,并努力为每个人管理尽可能多的数据, 其组织结构非常复杂。因此,该提供程序的 API 包含一个 广泛的协定类和接口,可促进数据检索和 修改。

本指南介绍以下内容:

  • 提供程序基本结构。
  • 如何从提供程序检索数据。
  • 如何修改提供程序中的数据。
  • 如何编写用于将数据从服务器同步到 联系人提供程序。

本指南假定您了解 Android 内容提供程序的基础知识。如需详细了解 Android 内容提供程序,请阅读内容提供程序基础知识指南。

联系人提供程序结构

联系人提供程序是一个 Android content provider 组件。它保留了三种类型的联系人数据,每种数据都对应提供程序提供的一个表,如图 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),这样您的应用才能添加原始联系人行。 您可以在文档中向用户解释此要求,也可提示用户添加类型和名称,或者同时采用这两种措施。账号类型和账号名称 。

原始联系人数据来源

为理解原始联系人的工作方式,假设有一位用户“Emily Dickinson”,她的设备上定义了以下三个用户账号:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Twitter 账号“belle_of_amherst”

该用户已在“账号”设置中为这三个账号启用了同步通讯录

假定 Emily Dickinson 打开一个浏览器窗口,以 emily.dickinson@gmail.com 身份登录 Gmail,然后打开“联系人”,并添加“Thomas Higginson”。随后,她登录 Gmail emilyd@gmail.com并向“Thomas Higginson”发送一封电子邮件, 将他添加为联系人。她还在 Twitter 上关注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。

以上操作的结果是,联系人提供程序会创建三个原始联系人:

  1. 第一个原始联系人对应“Thomas Higginson”,关联账号 emily.dickinson@gmail.com。 用户账号类型为 Google。
  2. “Thomas Higginson”的第二个原始联系人与 emilyd@gmail.com 相关联。 用户账号类型也是 Google。第二个原始联系人 尽管该姓名与以前的姓名相同,但由于此人是 不同的用户账号。
  3. 第三个原始联系人对应“Thomas Higginson”,关联账号“belle_of_amherst”。用户账号类型为 Twitter。

数据

如前所述,原始联系人的数据存储在一个 ContactsContract.Data 行中,该行链接到原始联系人的 _ID 值。基于此,一位原始联系人便可拥有多个数据类型相同的实例,如电子邮件地址或电话号码。例如,如果对应 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 个名称为 DATA1 到 的通用列 正式版 DATA15,以及另外四个通用 只应由同步使用的 SYNC1SYNC4 列 适配器。通用列名称常量始终有效,无论 行包含的数据。

DATA1 列已编入索引。联系人提供程序始终将此列用于以下用途: 提供商期望的数据将是查询最频繁的目标。例如: ,则此列包含实际的电子邮件地址。

按照惯例,DATA15 列为预留列,用于存储照片缩略图等二进制大型对象 (BLOB) 数据。

类型专用列名称

为便于处理特定类型行的列,联系人提供程序还提供了在 ContactsContract.CommonDataKinds 子类中定义的类型专用列名称常量。这些常量只是为 为同一列名称指定不同的常量名称,这有助于您访问 特定类型。

例如,ContactsContract.CommonDataKinds.Email 类为 ContactsContract.Data 行定义类型专用列名称常量,该行的 MIME 类型为 Email.CONTENT_ITEM_TYPE。该类包含常量 ADDRESS 表示电子邮件地址 列。 ADDRESS 为“data1”, 与列的通用名称相同。

注意:请勿使用具有提供程序某个预定义 MIME 类型的行向 ContactsContract.Data 表中添加您自己的自定义数据。否则您可能会丢失数据,或导致提供程序发生故障。例如,您不应添加 MIME 类型的行 Email.CONTENT_ITEM_TYPE 包含用户名,而不是 第 DATA1 列。如果您为该行使用自定义的 MIME 类型,则可自由定义您自己的类型专用列名称,并按意愿使用这些列。

图 2 显示了描述性列和数据列在 ContactsContract.Data 行,以及类型专用列名称“叠加”的方式 通用列名称

类型专用列名称如何映射到通用列名称

图 2. 类型专用列名称和通用列名称。

类型专用列名称类

表 2 列出了最常用的类型专用列名称类:

表 2. 类型专用列名称类

映射类 数据类型 备注
ContactsContract.CommonDataKinds.StructuredName 与该数据行关联的原始联系人的姓名数据。 一位原始联系人只有其中一行。
ContactsContract.CommonDataKinds.Photo 与该数据行关联的原始联系人的主要照片。 一位原始联系人只有其中一行。
ContactsContract.CommonDataKinds.Email 与该数据行关联的原始联系人的电子邮件地址。 一位原始联系人可有多个电子邮件地址。
ContactsContract.CommonDataKinds.StructuredPostal 与该数据行关联的原始联系人的邮政地址。 一位原始联系人可有多个邮政地址。
ContactsContract.CommonDataKinds.GroupMembership 将原始联系人链接到联系人提供程序内某个组的标识符。 组是账号类型和账号名称的一项可选功能。相关说明 如需了解详情,请参阅联系人群组部分。

通讯录

联系人提供程序会将所有账号类型和账号名称的原始联系人行进行合并,从而形成联系人。这样便于显示和修改 用户针对某个人收集了多少信息。联系人提供程序管理新联系人的创建 行,以及原始联系人与现有联系人行的汇总。系统不允许应用或同步适配器添加联系人,并且联系人行中的某些列为只读列。

注意:如果您尝试通过 insert() 向联系人提供程序添加联系人,则会获得一个 UnsupportedOperationException 异常。如果您尝试更新列 其状态显示为“只读”系统会忽略此次更新。

联系人提供程序会创建新的联系人,以响应新原始联系人的添加 都不匹配的现有联系人。如果某个现有原始联系人的数据发生变化,不再匹配其之前关联的联系人,则提供程序也会执行此操作。如果应用或同步适配器创建的新原始联系人确实与某位现有联系人匹配,则系统会将二者合并。

联系人提供程序会利用 Contacts 表内联系人行的 _ID 列,将联系人行链接至其原始联系人行。原始联系人表格的 CONTACT_IDContactsContract.RawContacts包含 _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,请使用替代方法 方法。例如,您可以使用 私有 content provider 或存储在您的应用或后端中的其他数据 系统。

如要验证应用的功能不受此更改的影响,您可以手动清除这些数据字段。为此,请在搭载 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 展示联系人提供程序如何适应联系人数据流。在标记为“sync adapters”(同步适配器)的方框中,每个适配器都以其账号类型命名。

联系人数据流

图 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_CONTACTS和 需要 WRITE_CONTACTS 权限才能读取和写入 需要 android.Manifest.permission#READ_PROFILE 和 android.Manifest.permission#WRITE_PROFILE 权限,可以进行读取和写入访问; 。

请注意,您应将用户的个人资料视为敏感个人资料。权限 android.Manifest.permission#READ_PROFILE 可让您访问设备用户的 可用于识别个人身份的数据请务必告知用户原因 您需要在应用的说明中添加用户个人资料访问权限。

要检索包含用户个人资料的联系人行, 调用 ContentResolver.query()。将内容 URI 设置为 CONTENT_URI,且不提供任何选择条件。您还可以使用此内容 URI 作为检索原始联系人或个人资料数据的基础 URI。例如,以下代码段用于检索个人资料的数据:

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 设备上,联系人提供程序会自动设置该值 应用会更新一行。

修改原始联系人表或数据表的同步适配器应始终向其使用的内容 URI 追加字符串 CALLER_IS_SYNCADAPTER。这可以防止提供程序将行标记为已更新。 否则,同步适配器修改似乎是本地修改, 会发送到服务器。

“1”- 自上次同步以来发生了更改,需要同步回服务器。
ContactsContract.RawContacts VERSION 此行的版本号。 每当行或其相关数据发生变化时,联系人提供程序都会自动增加此值。
ContactsContract.Data DATA_VERSION 此行的版本号。 每当数据行发生变化时,联系人提供程序都会自动增加此值。
ContactsContract.RawContacts SOURCE_ID 一个字符串值,用于在 中的账号上唯一标识此原始联系人 它的创建对象 当同步适配器创建新的原始联系人时,应将此列设置为该原始联系人在服务器中的唯一 ID。当 Android 应用创建新的原始联系人时,应将此列留空。这表明 它应该在服务器上创建一个新的原始联系人,并获取 SOURCE_ID 的值。

具体地讲,对于每个账号类型,该源 ID 都必须是唯一的,并且应在所有同步中保持稳定:

  • 唯一:账号的每个原始联系人都必须有自己的来源 ID。如果您不强制执行此要求,会在联系人应用中引发问题。 请注意,账号类型相同的两个原始联系人可以具有相同的源 ID。例如,原始联系人“Thomas Higginson”的 账号 emily.dickinson@gmail.com 可以具有相同的来源 id 作为原始联系人“Thomas Higginson”对于账号 emilyd@gmail.com
  • 稳定:来源 ID 是在线服务数据的永久组成部分, 原始联系人。例如,如果用户从“应用”设置中清除存储的联系人数据并重新同步,则恢复的原始联系人的源 ID 应与先前相同。如果您不强制执行此要求,快捷方式将停止工作。
ContactsContract.Groups GROUP_VISIBLE “0”- 此群组中的联系人在 Android 应用界面中不应处于可见状态。 此列用于兼容那些允许用户在以下位置隐藏联系人的服务器: 特定群体
“1”:系统允许此组中的联系人在应用界面中处于可见状态。
ContactsContract.Settings UNGROUPED_VISIBLE “0”:对于此账号和账号类型,未归入组的联系人在 Android 应用界面中处于不可见状态。 默认情况下,如果联系人的所有原始联系人都不属于某个组,则这些联系人将处于不可见状态 (原始联系人的群组成员资格由一个或多个 ContactsContract.CommonDataKinds.GroupMembership 行 在 ContactsContract.Data 表中)。 在 ContactsContract.Settings 表行中设置此标志 ,您可以强制显示未分组的联系人。 此标志的一个用途是显示不使用组的服务器的联系人。
“1”:对于此账号和账号类型,未归入组的联系人在应用界面中处于可见状态。
ContactsContract.SyncState (所有列) 此表用于存储同步适配器的元数据。 利用此表,您可以持续在设备中存储同步状态及其他与同步相关的数据。

联系人提供程序访问

本部分介绍了访问联系人提供程序数据时所遵循的准则,重点关注 以下:

  • 实体查询。
  • 批量修改。
  • 通过 intent 执行检索和修改。
  • 数据完整性。

该部分也更详细地介绍了如何通过同步适配器进行修改 联系人提供程序同步适配器

查询实体

联系人提供程序采用层次结构,有助于检索某一行以及与该行链接的所有“子”行。例如,如要显示某位联系人的所有信息,您可能需要检索某个 ContactsContract.Contacts 行的所有 ContactsContract.RawContacts 行,或者检索某个 ContactsContract.RawContacts 行的所有 ContactsContract.CommonDataKinds.Email 行。为方便起见,Google 通讯录 提供方提供实体结构,其作用类似于 表格。

实体类似于一个表,该表由父表及其子表中的选定列组成。 查询实体时,您需根据实体中的可用列提供投影和搜索条件。结果会得到一个包含以下内容的 Cursor: 检索到的每个子表行占一行。例如,如果您在 ContactsContract.Contacts.Entity 中查询某个联系人姓名,以及使用该姓名的所有原始联系人的所有 ContactsContract.CommonDataKinds.Email 行,则会获得一个 Cursor,每个 ContactsContract.CommonDataKinds.Email 行在其中都有一行与之对应。

实体可简化查询。使用实体时,您可以一次性检索联系人或原始联系人的所有联系人数据,而不必先通过查询父表获得 ID,然后通过该 ID 查询子表。此外,联系人提供程序可通过单一事务处理实体查询,从而确保所检索数据的内部一致性。

注意:实体通常不包含父实体的所有列, 子表。如果您尝试使用的列名称不在列名称列表中 常量,您会得到一个 Exception

以下代码段展示了如何检索某个联系人的所有原始联系人行。代码段 是具有两个 activity(“主”)的大型应用的一部分和“详情”主 activity 显示联系人行列表;当用户选择某一行时,该 activity 会将该行 ID 发送至详情 activity。详情 activity 使用 ContactsContract.Contacts.Entity 显示与所选联系人关联的所有原始联系人中的所有数据行。

以下代码段摘自“详情”activity:

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 获取数据,以进行显示或做进一步处理。

批量修改

如果可能,您应该在 Google Cloud 上的联系人提供程序中插入、更新和删除数据, 通过创建 ArrayListContentProviderOperation 对象和调用 applyBatch()。由于联系人提供程序是在 applyBatch() 中通过单一事务执行所有操作,因此您的修改绝不会使联系人存储区出现不一致问题。批量修改还有助于在以下位置插入原始联系人及其详细数据: 。

注意:如要修改单个原始联系人,可以考虑向设备的联系人应用发送一个 intent,而不是在您的应用中处理修改。通过 intent 执行检索和修改部分对此操作做了更详尽的描述。

挂起点

包含大量操作的批量修改可能会阻塞其他进程, 进而导致整体用户体验不佳为将所有您想执行的修改操作尽量分散到各个集合中,并同时防止其阻塞系统,您需要为一项或多项操作设置屈服点。 挂起点是一个 ContentProviderOperation 对象,其 isYieldAllowed() 值设置为 true。当联系人提供程序遇到挂起点时,它会暂停其工作 让其他进程运行并关闭当前事务。当提供程序再次启动时, 继续执行 ArrayList 中的下一项操作, 交易。

屈服点会导致每次调用 applyBatch()。因此,您应为一组相关行的最后一项操作设置挂起点。 例如,您应该为一组操作中添加原始联系人行及其关联数据行的最后一项操作设置挂起点,或者针对一组与某位联系人相关的行的最后一项操作设置屈服点。

挂起点也是一个原子操作单元。两个收益点之间的所有访问都会 成功或失败。如果您不设置任何收益点 原子操作是指整个批量操作。如果您使用“收益点” 避免系统性能下降,同时又能确保 操作是原子性的

修改向后引用

在将新的原始联系人行及其关联的数据行作为一组 ContentProviderOperation 对象插入时,您需将原始联系人的 _ID 值作为 RAW_CONTACT_ID 值插入,从而将数据行链接到原始联系人行。不过,您在数据行创建 ContentProviderOperation 时仍无法使用该值,因为您尚未对原始联系人行应用 ContentProviderOperation。为解决此问题, ContentProviderOperation.Builder 类包含方法 withValueBackReference()。 借助该方法,您可以插入或修改包含上一次操作结果的列。

withValueBackReference() 方法有两个参数:

key
键值对的键。此参数的值应为您要修改的表中某一列的名称。
previousResult
数组中某个值的索引(从 0 开始) ContentProviderResult 个对象来自 applyBatch()。在应用批处理操作时,每个操作的结果均存储在结果的中间数组内。previousResult 值是其中某个结果的索引,并且需通过 key 值对其进行检索和存储。这样,您便可插入一条新的原始联系人记录,并取回其 _ID 值,然后在添加 ContactsContract.Data 行时“向后引用”该值。

系统会在您首次调用时创建整个结果数组, applyBatch(), 且大小等于ArrayList 您提供的 ContentProviderOperation 对象。不过,结果数组中的所有元素均已设为 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. 创建一个适合以下场景的 ContentProviderOperation.Builder 对象: 使用 方法强制执行约束, newAssertQuery(Uri)。对于内容 URI 使用RawContacts.CONTENT_URI 并附加原始联系人的 _ID
  3. 对于 ContentProviderOperation.Builder 对象,请调用 withValue(),将 VERSION 列与您刚检索的版本号进行比较。
  4. 对于同一 ContentProviderOperation.Builder,请调用 withExpectedCount(),确保此断言只对一行进行测试。
  5. 调用 build() 以创建 ContentProviderOperation 对象,然后将此对象添加为 ArrayList 中您要传递的第一个对象 applyBatch()
  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
    }

通过 Intent 执行检索和修改

通过向设备的通讯录应用发送 intent,您可以间接访问通讯录提供程序。intent 会启动设备的联系人应用界面,用户可以在该界面中执行与联系人相关的操作。通过这种访问方式,用户可以:

  • 从列表中选取一位联系人并将其返回给您的应用以执行进一步操作。
  • 修改现有联系人的数据。
  • 为其任一账户插入新原始联系人。
  • 删除联系人或联系人数据。

如果用户正在插入或更新数据,您可以先收集数据,然后将其作为 是 intent 的一部分。

当您使用 intent 通过设备的联系人应用访问联系人提供程序时, 而无需自行编写界面或代码来访问提供程序。您也无需请求对提供程序的读取或写入权限。设备的联系人应用可以 授予联系人读取权限,且您要修改 则不必拥有写入权限。

下面详细介绍了通过发送 intent 来访问提供程序的一般过程, <ph type="x-smartling-placeholder"></ph> Content Provider 基础知识指南的“通过 intent 访问数据”部分。表 4 总结了可用任务中所用的操作、MIME 类型和数据值,而可用于 putExtra() 的 Extra 值则列在 ContactsContract.Intents.Insert 参考文档中:

表 4. 联系人提供程序 intent。

任务 操作 数据 MIME 类型 备注
从列表中选择联系人 ACTION_PICK 以下各项之一: <ph type="x-smartling-placeholder"> 未使用 显示原始联系人列表或某位原始联系人的数据列表,具体取决于您提供的内容 URI 类型。

致电 startActivityForResult(), 该函数用于返回所选行的内容 URI。URI 的形式为 表格的内容 URI,附加了该行的 LOOKUP_ID。 设备的联系人应用会在 activity 的生命周期内将读取和写入权限授予给此内容 URI。如需了解详情,请参阅内容提供程序基础知识指南。

插入新原始联系人 Insert.ACTION 不适用 RawContacts.CONTENT_TYPE,一组原始联系人的 MIME 类型。 显示设备“通讯录”应用的添加联系人屏幕。系统会显示您添加到 intent 中的 extra 值。如果是随 startActivityForResult() 发送,系统会将新添加的原始联系人的内容 URI 传回给 activity 的 onActivityResult() 回调方法并作为后者 Intent 参数的“data”字段。如需获取该值,请调用 getData()
修改联系人 ACTION_EDIT CONTENT_LOOKUP_URI: 该联系人。用户可使用编辑 activity 编辑任何与此联系人关联的数据。 Contacts.CONTENT_ITEM_TYPE,单个联系人。 显示“通讯录”应用中的“修改联系人”屏幕。您添加的 extra 值 发送到 intent 的选项。当用户点击完成以保存 修改后,您的 activity 会返回前台。
显示一个同样可以添加数据的选择器。 ACTION_INSERT_OR_EDIT N/A CONTENT_ITEM_TYPE 此 intent 始终显示“联系人”应用的选择器屏幕。用户可以执行以下任一操作 选择要修改的联系人或添加新的联系人。根据用户的选择,系统会显示“修改”或“添加”界面,还会显示您使用 intent 传递的 extra 数据。如果您的应用显示电子邮件或电话号码等联系人数据,请使用 此 intent 允许用户将数据添加到现有联系人。 联系人

注意:无需通过此 intent 的 extra 数据发送姓名值,因为用户总是会选取现有姓名或添加新姓名。此外, 如果您发送姓名,并且用户选择进行修改,那么“通讯录”应用 显示您发送的名称,覆盖之前的值。如果用户未注意此情况便保存了编辑,则旧值会丢失。

设备的联系人应用不允许您使用 intent 删除原始联系人或其任何数据。要删除原始联系人,请使用 ContentResolver.delete()ContentProviderOperation.newDelete()

以下代码段展示了如何构建和发送一个 intent,用于插入新的原始 联系人和数据:

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.CommonDataKinds.StructuredName 行 每添加 ContactsContract.RawContacts 行。
不含ContactsContract.RawContacts ContactsContract.CommonDataKinds.StructuredName 行中的 ContactsContract.Data 个表可能会导致以下错误: 汇总。
务必将新 ContactsContract.Data 行链接到其父级 ContactsContract.RawContacts 行。
如果 ContactsContract.Data 行未链接到 ContactsContract.RawContacts,则其在设备的通讯录应用中将处于不可见状态,而且这可能会导致同步适配器出现问题。
只更改您所拥有的原始联系人的数据。
请注意,联系人提供程序所管理的数据通常来自多个不同账号类型/在线服务。您需要确保您的应用仅修改或删除归您所有的行的数据,并且仅通过您控制的账号类型和账号名称插入数据。
始终使用 ContactsContract 中定义的常量及其 内容 URI、URI 路径、列名称、MIME 类型和 TYPE 值。
使用这些常量有助于您避免错误。你还会收到编译器的相关通知 如果有任何常量被弃用,则发出警告。

自定义数据行

通过创建和使用自己的自定义 MIME 类型,您可以在 ContactsContract.Data 表中插入、编辑、删除和检索您的自有数据行。这些行仅限使用 ContactsContract.DataColumns 中定义的列,但您可以将自己的类型专用列名称映射到默认列名称。设备的通讯录应用会显示这些行的数据,但用户无法对其进行编辑或删除,亦无法添加其他数据。如要让用户修改您的自定义数据行,您必须在自己的应用中提供编辑 activity。

如需显示自定义数据,请提供一个 contacts.xml 文件,其中须包含一个 <ContactsAccountType> 元素,及其一个或多个 <ContactsDataKind> 子元素。有关详情,请参阅 第 <ContactsDataKind> element 部分。

如需详细了解自定义 MIME 类型,请参阅 <ph type="x-smartling-placeholder"></ph> 创建 Content Provider 指南。

联系人提供程序同步适配器

联系人提供程序专门设计用于处理设备与在线服务之间的联系人数据同步。借助同步功能,用户可以将现有数据下载到新设备,并将现有数据上传到新账号。 同步还能确保用户手握最新数据,无需考虑数据增加和更改的来源。同步的另一个优点是 联系人数据。

尽管您可以通过多种方式实现同步,但 Android 系统提供了 可自动执行以下任务的插件同步框架:

  • 检查网络可用性。
  • 根据用户偏好安排和执行同步。
  • 重启已停止的同步。

如要使用此框架,您需提供同步适配器插件。每个同步适配器都专用于某个服务和内容提供程序,但可以处理同一服务的多个账号名称。该框架还允许同一服务和提供程序拥有多个同步适配器。

同步适配器类和文件

您需要将同步适配器作为 AbstractThreadedSyncAdapter 的子类进行实现,并作为 Android 应用的一部分进行安装。系统会通过应用清单文件中的元素以及清单文件指向的特殊 XML 文件,了解同步适配器的相关信息。XML 文件定义了 在线服务的账号类型和内容提供方的权限,两者结合在一起, 是适配器的唯一标识。只有当用户为同步适配器的账号类型添加账号,并为与同步适配器同步的内容提供程序启用同步后,同步适配器才会激活。届时,系统会开始管理适配器, 必要时调用它,以在 content provider 与服务器之间同步。

注意:通过将账号类型用作同步适配器标识的部分内容,系统可检测出从同一账号类型访问不同服务的同步适配器,并对其进行分类。例如,Google 在线服务的同步适配器都拥有相同的 账号类型 com.google。用户将 Google 账号添加到自己的设备后, 下面列出了已安装的所有 Google 服务同步适配器;每个同步适配器 与设备上的其他内容提供程序同步。

因为大多数服务都要求用户在访问 数据,Android 系统提供了一个身份验证框架,该框架类似于 与同步适配器框架配合使用。该身份验证框架使用的插件身份验证器是 AbstractAccountAuthenticator 的子类。由身份验证器负责 在以下步骤中确认用户身份:

  1. 收集用户的姓名、密码或类似信息 credentials)。
  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。反过来,此文件指定 要与联系人提供程序同步的 Web 服务的 URI, 和 Web 服务的账号类型。
  • 可选:身份验证器的 <meta-data> 元素指向 XML 文件 res/xml/authenticator.xml。而该文件会指定 账号类型,以及用于登录账号的界面资源, 在身份验证过程中显示的信息。在此元素中指定的账号类型必须与为同步适配器指定的账号类型相同。

社交流数据

android.provider.ContactsContract.StreamItems 表和 android.provider.ContactsContract.StreamItemPhotos 表管理着来自社交网络的传入数据。您可以编写同步适配器,将自己社交网络中的流数据添加至这些表内,也可读取表中的流数据并将其显示在自己的应用中,或同时执行这两种操作。借助这些功能,您的社交网络 服务和应用程序可以集成到 Android 的社交网络体验中。

社交流文本

流式传输项始终与原始联系人相关联。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID 会链接到原始联系人的 _ID 值。原始文件的账号类型和账号名称 联系人也存储在流项目行中。

将数据流的数据存储在以下列中:

android.provider.ContactsContract.StreamItems 列#ACCOUNT_TYPE
必需。与该流项目关联的原始联系人的用户账号类型。请记得在插入流项目时设置此值。
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必需。与此关联的原始联系人的用户账号名称 流式传输内容。请记得在插入流项目时设置此值。
标识符列
必需。执行以下操作时,您必须插入以下标识符列: 插入流项目: <ph type="x-smartling-placeholder">
    </ph>
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: 此流式传输的联系人的 android.provider.BaseColumns#_ID 值 相关联的商品。
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: 调用 与此流项目关联的联系人。
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: 此流式传输的原始联系人的 android.provider.BaseColumns#_ID 值 相关联的商品。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
可选。存储您可以在流项目开头显示的摘要信息。
android.provider.ContactsContract.StreamItems 列#TEXT
流项目的文本,或为项目来源发布的内容,或是对生成流项目的某项操作的描述。此列可以包含 任何可通过 fromHtml()。提供程序可能会截断或 ellipsize 长内容,但会尽量避免破坏标签。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
一个包含流项目插入时间或更新时间的文本字符串,以从公元纪年开始计算的毫秒数形式表示。用于插入或更新信息流内容的应用 负责维护此列;它不会自动进行维护, 联系人提供程序。

如需显示您的流项目的标识信息,请使用 android.provider.ContactsContract.StreamItemsColumns#RES_ICON、android.provider.ContactsContract.StreamItemsColumns#RES_LABEL 和 android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE 链接到应用中的资源。

android.provider.ContactsContract.StreamItems 表也包含这些列 android.provider.ContactsContract.StreamItemsColumns#SYNC1 至 android.provider.ContactsContract.StreamItemsColumns#SYNC4 仅供 同步适配器

社交流照片

android.provider.ContactsContract.StreamItemPhotos 表存储与 与流项相关联该表的 android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID 列会链接到 android.provider.ContactsContract.StreamItems 表的 _ID 列中的值。照片引用存储在表中的以下列:

android.provider.ContactsContract.StreamItemPhotos#PHOTO 列(一个二进制大型对象)。
照片的二进制表示,为便于存储和显示,由提供程序调整了大小。 此列可用于向后兼容旧版通讯录 使用它来存储照片的提供方。不过,在当前版本中,您不应使用此列来存储照片。而应使用 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID 或 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(下文对两者都做了描述)将照片存储在一个文件内。此列现在 包含照片的缩略图,可供读取。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
原始联系人照片的数字标识符。将此值附加到常量 DisplayPhoto.CONTENT_URI 获取指向单个照片文件的内容 URI,然后调用 openAssetFileDescriptor(),用于获取照片文件的句柄。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
直接指向此行所表示照片的照片文件的内容 URI。 使用此 URI 调用 openAssetFileDescriptor() 以获取照片文件的句柄。

使用社交流表

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

  • 这些表需要额外的访问权限。如需读取它们,您的应用 必须具有 android.Manifest.permission#READ_SOCIAL_STREAM 权限。接收者 那么您的应用必须具有相应权限 android.Manifest.permission#WRITE_SOCIAL_STREAM。
  • 对于 android.provider.ContactsContract.StreamItems 表,为每位原始联系人存储的行数有限。达到此上限后 联系人提供程序通过自动删除来为新的流项目行腾出空间 包含最早的 android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP。如需获取该限制,请发出对内容 URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI 的查询。您可以离开 除了设置为 null 的内容 URI 之外的所有参数。查询会返回一个 Cursor,其中包含一行,并且只有 android.provider.ContactsContract.StreamItems#MAX_ITEMS 一列。

android.provider.ContactsContract.StreamItems.StreamItemPhotos 类定义了 android.provider.ContactsContract.StreamItemPhotos 的子表,其中包含某个流项目的照片行。

社交流交互

由联系人提供程序管理的社交流数据,与 联系人应用程序,提供了一种连接您的社交网络系统的有效方式 现有的联系人。可使用以下功能:

  • 通过同步功能将您的社交网络服务与联系人提供程序同步 您可以检索用户联系人的近期活动,并将其存储在 android.provider.ContactsContract.StreamItems 和 android.provider.ContactsContract.StreamItemPhotos 表,可供稍后使用。
  • 除了定期同步外,您还可以在用户选择某位联系人进行查看时触发您的同步适配器以检索更多数据。这样,您的同步适配器 检索高分辨率照片和联系人的最新流项目。
  • 通过在设备的联系人应用和通讯录中注册通知 Provider,您可以在查看联系人时接收一个 intent,此时 通过您的服务更新联系人的状态。这种方法可能更快, 与使用同步适配器进行完全同步相比,带宽更高。
  • 在查看设备联系人应用中的联系人时,用户可将其添加至您的社交网络服务。您可以通过“邀请联系人”来启用特征, 您可以通过结合使用一项活动将现有联系人添加到您的 以及一个 XML 文件,该文件提供设备的联系人应用以及 包含应用详情的联系人提供程序。

流项目与联系人提供程序的定期同步与 其他同步。如需详细了解同步,请参阅联系人提供程序同步适配器部分。接下来的两部分介绍如何注册通知和邀请联系人。

通过注册处理社交网络查看

要注册您的同步适配器,以便在用户查看 由同步适配器管理:

  1. 在项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。如果您已有此文件,可以跳过此步骤。
  2. 在此文件中,添加元素 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">。 如果此元素已存在,则可以跳过此步骤。
  3. 如要注册一项服务,以便在用户于设备的联系人应用中打开某位联系人的详情页面时通知该服务,请为该元素添加 viewContactNotifyService="serviceclass" 属性,其中 serviceclass 是该服务的完全限定类名,并且应由该服务接收来自设备联系人应用的 intent。对于通知程序服务,请使用扩展 IntentService 的类,以便该服务能够接收 intent。传入 intent 中的数据包含原始 intent 的内容 URI, 用户点击的链接。您可以使用通知程序服务绑定到您的同步适配器,然后调用该同步适配器更新原始联系人的数据。

如需注册在用户点击流项目和/或照片时调用的 Activity,请执行以下操作:

  1. 在项目的 res/xml/ 中创建名为 contacts.xml 的文件 目录。如果您已有该文件,可跳过此步骤。
  2. 在此文件中,添加 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。 如果此元素已存在,则可以跳过此步骤。
  3. 如需注册您的某个 activity,以便处理用户在 设备的联系人应用,请添加属性 对元素执行 viewStreamItemActivity="activityclass",其中 activityclass 是 activity 的完全限定类名 应从设备的联系人应用接收 intent。
  4. 要注册您的某个 Activity,以处理用户在 设备的联系人应用,请添加属性 对元素执行 viewStreamItemPhotoActivity="activityclass",其中 activityclass 是 activity 的完全限定类名 应从设备的联系人应用接收 intent。

<ContactsAccountType> 元素部分对 <ContactsAccountType> 元素进行了更详细的介绍。

传入 intent 包含用户所点击的项目或照片的内容 URI。 要针对文本项和照片分别使用不同的 activity,请在同一文件中使用这两个属性。

与您的社交网络服务进行交互

在邀请联系人访问您的社交网络网站时,用户无需离开设备的联系人应用。您可以让设备的联系人应用发送一个 intent,用于邀请 您的某个活动。如需设置此模式,请执行以下操作:

  1. 在项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。如果您已有此文件,可以跳过此步骤。
  2. 在此文件中,添加元素 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">。 如果该元素已存在,可跳过此步骤。
  3. 添加以下属性: <ph type="x-smartling-placeholder">
      </ph>
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    activityclass 值是 应接收该 intent 的 activity。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 组件和界面标签,以便用户邀请他们的一位联系人加入社交网络、在其某个社交网络流更新时通知用户,以及执行其他操作。

请注意,这些属性不需要属性前缀 android:<ContactsAccountType> 个。

属性

inviteContactActivity
您的应用中某个 activity 的完全限定类名,您想要在用户于设备的联系人应用中选择 Add connection 时激活该 activity。
inviteContactActionLabel
为在 inviteContactActivity(位于添加连接菜单中)。 例如,您可以使用字符串“Follow in my network”。您可以使用字符串资源 此标签的标识符。
viewContactNotifyService
您的应用中应接收某个服务的完全限定类名 通知。此通知是由设备的 “通讯录”应用;它允许您的应用推迟数据密集型操作 直到需要它们为止例如,您的应用可以对此通知做出响应 通过读取并显示联系人的高分辨率照片和最近的照片, 社交流项目。社交流交互部分对此功能做了更详尽的描述。
viewGroupActivity
您的应用中某个可显示组信息的 activity 的完全限定类名。当用户点击设备联系人应用中的组标签时,系统会显示此 activity 的界面。
viewGroupActionLabel
联系人应用为某个界面控件显示的标签,用户可通过该控件查看您应用中的组。

此属性允许使用字符串资源标识符。

viewStreamItemActivity
应用中某个 activity 的完全限定类名,设备所在的 当用户点击原始联系人的流项目时,会启动联系人应用程序。
viewStreamItemPhotoActivity
您的应用中某个 activity 的完全限定类名,当用户点击原始联系人流项目中的照片时,设备的联系人应用会启动该 activity。

<ContactsDataKind>元素

<ContactsDataKind> 元素控制应用 自定义数据行。其语法如下:

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

包含在:

<ContactsAccountType>

说明:

此元素用于让联系人应用将自定义数据行的内容显示为原始联系人详细信息的一部分。每个 <ContactsDataKind> 子元素 的 <ContactsAccountType> 表示您要同步的自定义数据行的类型 适配器会添加到 ContactsContract.Data 表中。请为您使用的每个自定义 MIME 类型添加一个 <ContactsDataKind> 元素。如果您不想显示任何自定义数据行的数据,则无需添加该元素。

属性

android:mimeType
您为某个自定义数据行类型定义的自定义 MIME 类型 ContactsContract.Data 表。例如,值 “vnd.android.cursor.item/vnd.example.locationstatus”可以是自定义项目 记录联系人最近一次的已知位置的数据行的 MIME 类型。
android:icon
联系人应用在您的数据旁显示的 Android 可绘制对象资源。该属性用于向用户指示数据来自您的服务。
android:summaryColumn
从数据行中检索到的两个值中第一个值的列名。该值显示为该数据行的第一个输入行。第一行专用作数据摘要,但它是可选项。另请参阅 android:detailColumn
android:detailColumn
从数据行中检索到的两个值中第二个值的列名。该值显示为该数据行的第二个输入行。另请参阅 android:summaryColumn

其他联系人提供程序功能

除了上文描述的主要功能外,联系人提供程序还为处理联系人数据提供了下列实用功能:

  • 联系人群组
  • 照片功能

联系人群组

联系人提供程序可以选择性地为相关联系人集合添加标签 group 数据。如果与某个用户账号关联的服务器想要维护组,则与该账号的账号类型对应的同步适配器应在联系人提供程序与服务器之间传送组数据。当用户向服务器添加新联系人,然后将其放入新组时,同步适配器必须将该新组添加到 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 表中,社交流照片部分对该表做了更详尽的描述。