Android短信中的Contacts类

 

 

 

Contact provider

      Contact provider是一个强大而又灵活的 Android 组件,用于管理设备上有关联系人数据的中央存储库。 Contact provider是您在设备的联系人应用中看到的数据源,您也可以在自己的应用中访问其数据,并可在设备与在线服务之间传送数据。 提供程序储存有多种数据源,由于它会试图为每个联系人管理尽可能多的数据,因此造成其组织结构非常复杂。 为此,该提供程序的 API 包含丰富的协定类和接口,为数据检索和修改提供便利。

本指南介绍下列内容:

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

Contact provider(联系人提供程序组织)


联系人提供程序是 Android 内容提供程序的一个组件。它保留了三种类型的联系人数据,每一种数据都对应提供程序提供的一个表,如图 1 所示:
这里写图片描述
图 1. 联系人提供程序表结构。

这三个表通常以其协定类的名称命名。这些类定义表所使用的内容 URI、列名称及列值相应的常量:

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

ContactsContract.RawContacts 
包含联系人数据摘要的行,针对特定用户帐户和类型。

ContactsContract.Data 表
包含原始联系人详细信息(例如电子邮件地址或电话号码)的行。

由 ContactsContract中的协定类表示的其他表是辅助表,Contact provider利用它们来管理其操作,或为设备的联系人或电话应用中的特定功能提供支持。

表 1. 重要的原始联系人列。

列名称 用途 备注
ACCOUNT_NAME 作为该原始联系人来源的帐户类型的帐户名称。 例如,Google 帐户的帐户名称是设备所有者的某个 Gmail 地址 此名称的格式专用于其帐户类型。它不一定是电子邮件地址。
ACCOUNT_TYPE 作为该原始联系人来源的帐户类型。例如,Google 帐户的帐户类型是 com.google。 请务必使用您拥有或控制的域的域标识符限定您的帐户类型。 这可以确保您的帐户类型具有唯一性。 提供联系人数据的帐户类型通常关联有同步适配器,用于与Contact provider进行同步。
DELETED 提供联系人数据的帐户类型通常关联有同步适配器,用于与Contact provider进行同步。 此标志让Contact provider能够在内部保留该行,直至同步适配器能够从服务器删除该行,然后再从存储库中最终删除该行。

Original Contacts(原始联系人)


一个原始联系人表示来自某一帐户类型和帐户名称、有关某个联系人的数据。 由于Contact provider允许将多个在线服务作为某一联系人的数据源,因此它允许同一联系人对应多个原始联系人。 借助支持多个原始联系人的特性,用户还可以将某一联系人在帐户类型相同的多个帐户中的数据进行合并。

原始联系人的大部分数据并不存储在 ContactsContract.RawContacts 表内,而是存储在 ContactsContract.Data 表中的一行或多行内。每个数据行都有一个 Data.RAW_CONTACT_ID 列,其中包含其父级 ContactsContract.RawContacts 行的 android.provider.BaseColumns#_ID RawContacts._ID 值。

说明
以下是关于 ContactsContract.RawContacts 表的重要说明:

原始联系人的姓名并不存储其在 ContactsContract.RawContacts 中的行内,而是存储在 ContactsContract.Data 表的 ContactsContract.CommonDataKinds.StructuredName 行内。一个原始联系人在 ContactsContract.Data 表中只有一个该类型的行。 注意:要想在原始联系人行中使用您自己的帐户数据,必须先在 AccountManager 中注册帐户。 为此,请提示用户将帐户类型及其帐户名称添加到帐户列表。 如果您不这样做,联系人提供程序将自动删除您的原始联系人行。

例如,如果您想让您的应用为您域名为 com.example.dataservice、基于 Web 的服务保留联系人数据,并且您的服务的用户帐户是 [email protected],则用户必须先添加帐户“类型”(com.example.dataservice) 和帐户“名称”([email protected]),然后您的应用才能添加原始联系人行。 您可以在文档中向用户解释这项要求,也可以提示用户添加类型和名称,或者同时采用这两种措施。

Original contact data sources(原始联系人数据来源)

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

[email protected] [email protected] Twitter 帐户“belle_of_amherst”

该用户已在 Accounts 设置中为全部三个帐户启用了 Sync Contacts。

假定 Emily Dickinson 打开一个浏览器窗口,以 [email protected] 身份登录 Gmail,然后打开 “联系人”,并添加“Thomas Higginson”。后来,她以 [email protected] 身份登录 Gmail,并向“Thomas Higginson”发送一封电子邮件,此操作会自动将他添加为联系人。 她还在 Twitter 上关注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。

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

第一个原始联系人对应“Thomas Higginson”,关联帐户 [email protected]。 用户帐户类型是 Google。 第二个原始联系人对应“Thomas Higginson”,关联帐户 [email protected]。 用户帐户类型也是 Google。由于添加的联系人对应的用户帐户不同,因此尽管名称与前一名称完全相同,也只能作为第二个原始联系人。 第三个原始联系人对应“Thomas Higginson”,关联帐户“belle_of_amherst”。用户帐户类型是 Twitter。

Data(数据)


如前文所做的说明,原始联系人的数据存储在一个 ContactsContract.Data 行中,该行链接到原始联系人的 _ID 值。这使一位原始联系人可以拥有多个具有相同数据类型的实例,例如电子邮件地址或电话号码。 例如,如果对应 [email protected] 的“Thomas Higginson”(关联 Google 帐户 [email protected] 的 Thomas Higginson 的原始联系人行)的住宅电子邮件地址为 [email protected],办公电子邮件地址为 [email protected],则联系人提供程序会存储这两个电子邮件地址行,并将它们都链接到原始联系人。

请注意,这个表中存储了不同类型的数据。显示姓名、电话号码、电子邮件、邮政地址、照片以及网站明细行都可以在 ContactsContract.Data 表中找到。 为便于管理这些数据, ContactsContract.Data 表为一些列使用了描述性名称,为其他列使用了通用名称。 使用描述性名称的列的内容具有相同的含义,与行中数据的类型无关,而使用通用名称的列的内容则会随数据类型的不同而具有不同的含义。

Descriptive Column Name(描述性列名称)

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

RAW_CONTACT_ID
该数据对应的原始联系人 _ID 列的值。 MIMETYPE
该行中存储的数据类型,以自定义 MIME(多用途互联网邮件扩展)类型表示。联系人提供程序使用了 ContactsContract.CommonDataKinds 子类中定义的 MIME 类型。 这些 MIME 类型为开源类型,可供与联系人提供程序协作的任何应用或同步适配器使用。 IS_PRIMARY
如果一个原始联系人可能具有多个这种类型的数据行, IS_PRIMARY 列会标记 包含该类型主要数据的数据行。例如,如果用户长按某个联系人的电话号码,并选择 Set default,则包含该号码的 ContactsContract.Data 行会将其 IS_PRIMARY 列设置为一个非零值。

General Column Name(通用列名称)

有 15 个通用列命名为 DATA1 至 DATA15,可普遍适用;还有四个通用列命名为 SYNC1 至 SYNC4,只应由同步适配器使用。 通用列名称常量始终有效,与行包含的数据类型无关。

DATA1 列为索引列。联系人提供程序总是在此列中存储其预期会成为最频繁查询目标的数据。 例如,在一个电子邮件行中,此列包含实际电子邮件地址。

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

Type-specific column names(类型专用列名称)

为便于处理特定类型行的列,Contact provider还提供了 ContactsContract.CommonDataKinds 子类中定义的类型专用列名称常量。 这些常量只是为同一列名称提供不同的常量名称,这有助于您访问特定类型行中的数据。

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

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

图 2 显示的是描述性列和数据列在 ContactsContract.Data 行中的显示情况,以及类型专用列名称“覆盖”通用列名称的情况
这里写图片描述
图 2. 类型专用列名称和通用列名称。

Type the name of the class dedicated column(类型专用列名称类)

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

表 2. 类型专用列名称类

映射类 数据类型 备注
ContactsContract.CommonDataKinds.StructuredName 与该数据行关联的原始联系人的姓名数据。 一位原始联系人只有其中一行。
ContactsContract.CommonDataKinds.Photo 与该数据行关联的原始联系人的主要照片。 一位原始联系人只有其中一行。
ContactsContract.CommonDataKinds.Email 与该数据行关联的原始联系人的电子邮件地址。 一位原始联系人可有多个电子邮件地址。
ContactsContract.CommonDataKinds.StructuredPostal 与该数据行关联的原始联系人的邮政地址。 一位原始联系人可有多个邮政地址。
ContactsContract.CommonDataKinds.GroupMembership 将原始联系人链接到Contact provider内其中一组的标识符。 组是帐户类型和帐户名称的一项可选功能。

Contacts(联系人)

Contact provider通过将所有帐户类型和帐户名称的原始联系人行合并来形成联系人。 这可以为显示和修改用户针对某一联系人收集的所有数据提供便利。 联系人提供程序管理新联系人行的创建,以及原始联系人与现有联系人行的合并。 系统不允许应用或同步适配器添加联系人,并且联系人行中的某些列是只读列。

注:如果您试图通过 insert() 向联系人提供程序添加联系人,会引发一个 UnsupportedOperationException 异常。 如果您试图更新一个列为“只读”的列,更新会被忽略。

如果添加的新原始联系人不匹配任何现有联系人,Contact provider会相应地创建新联系人。 如果某个现有原始联系人的数据发生了变化,不再匹配其之前关联的联系人,则提供程序也会执行此操作。 如果应用或同步适配器创建的新原始联系人“的确”匹配某位现有联系人,则新原始联系人将与现有联系人合并。

Contact provider通过 Contacts 表中联系人行的 _ID 列将联系人行与其各原始联系人行链接起来。 原始联系人表 ContactsContract.RawContacts 的 CONTACT_ID 列包含对应于每个原始联系人行所关联联系人行的 _ID 值。

ContactsContract.Contacts 表还有一个 android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 列,它是一个指向联系人行的“永久性”链接。 由于联系人提供程序会自动维护联系人,因此可能会在合并或同步时相应地更改联系人行的 android.provider.BaseColumns#_ID 值。 即使发生这种情况,合并了联系人 android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 的内容 URI CONTENT_LOOKUP_URI 仍将指向联系人行,这样,您就能使用 android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 保持指向“最喜爱”联系人的链接,以及执行其他操作。 该列具有其自己的格式,与 android.provider.BaseColumns#_ID 列的格式无关。

图 3 显示的是这三个主要表的相互关系。
这里写图片描述
图 3. 联系人表、原始联系人表与详细信息表之间的关系。

Data from the synchronization adapter(来自同步适配器的数据)


虽然用户是直接将联系人数据输入到设备中,但这些数据也会通过同步适配器从 Web 服务流入联系人提供程序中,这些同步适配器可自动化设备与服务之间的数据传送。 同步适配器在系统控制下在后台运行,它们会调用 ContentResolver 方法来管理数据。

在 Android 中,与同步适配器协作的 Web 服务通过帐户类型加以标识。 每个同步适配器都与一个帐户类型协作,但它可以支持该类型的多个帐户名称。 原始联系人数据来源部分对帐户类型和帐户名称做了简要描述。 下列定义提供了更多详细信息,并描述了帐户类型及帐户名称与同步适配器及服务之间的关系。

帐户类型
表示用户在其中存储数据的服务。在大多数时候,用户需要向服务验证身份。 例如,Google Contacts 是一个以代码 google.com 标识的帐户类型。 该值对应于 AccountManager 使用的帐户类型。

帐户名称
**表示某个帐户类型的特定帐户或登录名。**Google Contacts 帐户与 Google 帐户相同,都是以电子邮件地址作为帐户名称。 其他服务可能使用一个单词的用户名或数字 ID。

帐户类型不必具有唯一性。用户可以配置多个 Google Contacts 帐户并将它们的数据下载到联系人提供程序;如果用户为个人帐户名称和工作帐户名称分别设置了一组联系人,就可能发生这种情况。 帐户名称通常具有唯一性。 它们共同标识联系人提供程序与外部服务之间的特定数据流。

如果您想将服务的数据传送到联系人提供程序,则需编写您自己的同步适配器。

图 4 显示的是联系人提供程序如何融入联系人数据的流动。 在名为“同步适配器”的方框中,每个适配器都以其帐户类型命名。
这里写图片描述
图 4. 联系人提供程序数据流。

The Required Permissions(所需权限)


想要访问Contact provider的应用必须请求以下权限:

对一个或多个表的读取权限
READ_CONTACTS,在 AndroidManifest.xml 中指定,使用 元素作为 。 对一个或多个表的写入权限
WRITE_CONTACTS,在 AndroidManifest.xml 中指定,使用 元素作为 。
这些权限不适用于用户个人资料数据。

请切记,用户的联系人数据属于个人敏感数据。用户关心其隐私权,因此不希望应用收集有关其自身的数据或其联系人的数据。 如需权限来访问其联系人数据的理由并不充分,用户可能给您的应用作出差评或干脆拒绝安装。

User Profile(用户个人资料)


ContactsContract.Contacts 表有一行包含设备用户的个人资料数据。 这些数据描述设备的 user 而不是用户的其中一位联系人。 对于每个使用个人资料的系统,该个人资料联系人行都链接到某个原始联系人行。 每个个人资料原始联系人行可具有多个数据行。ContactsContract.Profile 类中提供了用于访问用户个人资料的常量。

访问用户个人资料需要特殊权限。除了进行读取和写入所需的 READ_CONTACTS 和 WRITE_CONTACTS 权限外,如果想访问用户个人资料,还分别需要 android.Manifest.permission#READ_PROFILE 和 android.Manifest.permission#WRITE_PROFILE 权限进行读取和写入访问。

请切记,您应该将用户的个人资料视为敏感数据android.Manifest.permission#READ_PROFILE 权限让您可以访问设备用户的个人身份识别数据。 请务必在您的应用的描述中告知用户您需要用户个人资料访问权限的原因。

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

// Sets the columns to retrieve for the user profile

mProjection = new String[]

    {

        Profile._ID,

        Profile.DISPLAY_NAME_PRIMARY,

        Profile.LOOKUP_KEY,

        Profile.PHOTO_THUMBNAIL_URI

    };

 

// Retrieves the profile from the Contacts Provider

mProfileCursor =

        getContentResolver().query(

                Profile.CONTENT_URI,

                mProjection ,

                null,

                null,

                null);

注:如果您要检索多个联系人行并想要确定其中一个是否为用户个人资料,请测试该行的 IS_USER_PROFILE 列。 如果该联系人是用户个人资料,则此列设置为“1”。

(Contacts Provide Metadata)Contacts Provide元数据


Contact Provide管理用于追踪存储库中联系人数据状态的数据。 这些有关存储库的元数据存储在各处,其中包括原始联系人表行、数据表行和联系人表行、 ContactsContract.Settings 表以及 ContactsContract.SyncState 表。 下表显示的是每一部分元数据的作用:

表 3. 联系人提供程序中的元数据

含义
ContactsContract.RawContacts DIRTY “0”:上次同步以来未发生变化。“1”:上次同步以来发生了变化,需要同步回服务器。 标记设备上因发生变化而需要同步回服务器的原始联系人。 当 Android 应用更新行时,联系人提供程序会自动设置该值。修改原始联系人表或数据表的同步适配器应始终向他们使用的内容 URI 追加字符串 CALLER_IS_SYNCADAPTER。 这可以防止提供程序将行标记为已更新。 否则,即使服务器是修改的来源,同步适配器修改仍显示为本地修改,并会发送到服务器。
ContactsContract.RawContacts VERSION 此行的版本号。 每当行或其相关数据发生变化时,联系人提供程序都会自动增加此值。
ContactsContract.Data DATA_VERSION 此行的版本号。 每当数据行发生变化时,联系人提供程序都会自动增加此值。
ContactsContract.RawContacts SOURCE_ID 一个字符串值,用于在创建此原始联系人的帐户中对该联系人进行唯一标识。 当同步适配器创建新原始联系人时,此列应设置为该原始联系人在服务器中的唯一 ID。 当 Android 应用创建新原始联系人时,应将此列留空。 这是为了向同步适配器表明,它应该在服务器上创建新原始联系人,并获取 SOURCE_ID 的值。具体地讲,对于每个帐户类型,该源 ID 都必须是唯一的,并且应在所有同步中保持稳定: 1. 唯一:帐户的每个原始联系人都必须有自己的源 ID。如果您不强制执行此要求,会在联系人应用中引发问题。请注意,帐户类型相同的两个原始联系人可以具有相同的源 ID。 例如,允许帐户 [email protected] 的原始联系人“Thomas Higginson”与帐户 [email protected] 的原始联系人“ThomasHigginson”具有相同的源 ID。 2.稳定:源 ID 是该原始联系人在在线服务中的数据的永久性组成部分。 例如,如果用户从应用设置中清除存储的联系人数据并重新同步,则恢复的原始联系人的源 ID 应与以前相同。 如果您不强制执行此要求,快捷方式将停止工作。
ContactsContract.Groups GROUP_VISIBLE “0”:此组中的联系人在 Android 应用 UI 中不应处于可见状态。“1”:系统允许此组中的联系人在应用 UI 中处于可见状态。 此列用于兼容那些允许用户隐藏特定组中联系人的服务器。
ContactsContract.Settings UNGROUPED_VISIBLE “0”:对于此帐户和帐户类型,未归入组的联系人在 Android 应用 UI 中处于不可见状态。“1”:对于此帐户和帐户类型,未归入组的联系人在应用 UI 中处于可见状态。 默认情况下,如果联系人的所有原始联系人都未归入组,则它们将处于不可见状态(原始联系人的组成员身份通过 ContactsContract.Data 表中的一个或多个 ContactsContract.CommonDataKinds.GroupMembership 行指示)。 通过在 ContactsContract.Settings 表行中为帐户类型和帐户设置此标志,您可以强制未归入组的联系人处于可见状态。 此标志的一个用途是显示不使用组的服务器上的联系人。
ContactsContract.SyncState (所有列) 此表用于存储同步适配器的元数据。 利用此表,您可以将同步状态及其他同步相关数据持久地存储在设备中。

Contacts Access Provider(联系人提供程序访问)


本节描述访问联系人提供程序中数据的准则,侧重于阐述以下内容:

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

Discover Entity(查询实体)

由于Contact Provider表是以层级形式组织,因此对于检索某一行以及与其链接的所有“子”行,往往很有帮助。 例如,要想显示某位联系人的所有信息,您可能需要检索某个 ContactsContract.Contacts 行的所有ContactsContract.RawContacts 行,或者检索某个 ContactsContract.RawContacts 行的所有 ContactsContract.CommonDataKinds.Email 行。 为便于执行此操作,Contact Provider提供了实体构造,其作用类似于表间的数据库连接。

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

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

注:实体通常不包含父表和子表的所有列。 如果您试图使用的列名称并未出现在实体的列名称常量列表中,则会引发一个 Exception。

以下代码段说明如何检索某位联系人的所有原始联系人行。该代码段是一个大型应用的组成部分,包含“主”和“详”两个 Activity。 主 Activity 显示一个联系人行列表;当用户选择一行时,该 Activity 会将其 ID 发送至详 Activity。 详 Activity 使用 ContactsContract.Contacts.Entity 显示与所选联系人关联的所有原始联系人中的所有数据行。

/*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    mContactUri = Uri.withAppendedPath(
            mContactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
 
    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity
 
    // Creates a new cursor adapter to attach to the list view
    mCursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            mFromColumns,                // the columns in the cursor that provide the data
            mToViews,                    // the views in the view item that display the data
            0);                          // flags
 
    // Sets the ListView's backing adapter.
    mRawContactList.setAdapter(mCursorAdapter);
...
@Override
public Loader<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
            mContactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

猜你喜欢

转载自blog.csdn.net/qq_35427437/article/details/85999719