ContactsProvider

ContactsProvider概述

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

联系人数据结构

ContactsProvider中提供了3种联系人数据,每一种数据对应一张联系人信息表,通过这三种联系人数据的组合可以完整的为我们提供联系人信息。下面是对联系人数据的详细分析

Contact

ContactsContract.Contacts <------> contacts 存储基本的联系人数据,基于raw_contacts数据的组合

ContactsProvider通过将所有帐户类型和帐户名称的raw_contact合并来形成contact。 为显示和修改用户针对某一联系人收集的所有数据提供便利

ContactsProvider管理新联系人行的创建,以及raw_contact与现有联系人行的合并。 系统不允许应用或同步适配器添加联系人,并且contact中的某些列是只读列

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

contact 的创建规则如下:

如果添加的新raw_contacts不匹配任何现有联系人,ContactsProvider会相应地创建新联系人

如果某个现有raw_contact的数据发生了变化,不再匹配其之前关联的联系人,则提供程序也会执行此操作

如果应用或同步适配器创建的新raw_contact的确匹配某位现有联系人,则新raw_contact将与现有联系人合并。

Raw Contacts

ContactsContract.RawContacts <------> raw_contacts 包含联系人数据摘要,针对特定用户帐户和类型

raw_contacts 中的contact_id和contact表中的_id对应

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

raw_contacts的大部分数据并不存储在 ContactsContract.RawContacts 表内,而是存储在 ContactsContract.Data 表中的一行或多行内。每个数据行都有一个 Data.RAW_CONTACT_ID 列,其中包含其父级 ContactsContract.RawContacts 行的 RawContacts._ID 值。

  • raw_contacts的姓名并不存储其在 ContactsContract.RawContacts 中的行内,而是存储在 ContactsContract.Data 表的 ContactsContract.CommonDataKinds.StructuredName 行内。一个raw_contacts在 ContactsContract.Data 表中只有一个该类型的行。

  • 要想在raw_contacts行中使用您自己的帐户数据,必须先在 AccountManager 中注册帐户。 为此,请提示用户将帐户类型及其帐户名称添加到帐户列表。 如果不这样做,ContactsProvider将自动删除您的raw_contacts行。

RawContacts中的主要列

列名 说明
contact_id 此数据所属的{@link ContactsContract.Contacts#_ID}的引用。
account_id 此数据对应accounts表中的_id值,用来引用该表中的account_name和account_type
sync1-4 此数据和联系人同步相关

Data

ContactsContract.Data <------> data 包含raw_contacts详细信息(例如电子邮件地址或电话号码)

raw_contacts的数据存储在一个 ContactsContract.Data 行中,该行链接到raw_contacts的 _ID 值。一个raw_contacts可以拥有多个具有相同data类型的实例,例如电子邮件地址或电话号码。

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

ContactsContract

/frameworks/base/core/java/android/provider/ContactsContract.java

ContactsContract是一个联系人数据协定类,该类中定义了联系人数据表接口,为操作数据联系人提供了所有的依据,因此了解该类即可了解联系人数据结构的全貌。其中三个主要的联系人数据表是ContactsContract.Contacts、ContactsContract.RawContacts、ContactsContract.Data

除了以上三个主要的表,ContactsContract 中的其他表是辅助表,ContactsProvider利用它们来管理其操作,或为设备的联系人或电话应用中的特定功能提供支持。

描述性列名称

以下是重要的描述性列名称:

列名 说明
raw_contact_id 该数据对应的raw_contacts表 _ID 列的值。
mimetype_id 该数据链接到了mimetypes表
is_primary 主要数据标记,例如:set default 后 该标记设置为非0的值

通用列名称

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

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

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

类型专用列名称

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

类型专用列名称类(下列类型都在ContactsContract类中定义):

映射类 数据类型 说明
CommonDataKinds.StructuredName 与该数据行关联的raw_contacts的姓名数据。 一位raw_contacts只有其中一行。
CommonDataKinds.Photo 与该数据行关联的raw_contacts的主要照片。 一位raw_contacts只有其中一行。
CommonDataKinds.Email 与该数据行关联的raw_contacts的电子邮件地址。 一位raw_contacts可有多个电子邮件地址。
CommonDataKinds.StructuredPostal 与该数据行关联的raw_contacts的邮政地址。 一位raw_contacts可有多个邮政地址。
CommonDataKinds.GroupMembership 将raw_contacts链接到ContactsProvider内其中一组的标识符。 组是帐户类型和帐户名称的一项可选功能。 联系人组部分对其做了更详尽的描述。

Profile_个人信息

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

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

可使用ContactsContract.Profile.CONTENT_URI,对Profile数据进行访问

注:Profile数据存储在profile.db数据库中

ContactsProvider数据操作

操作权限

想要访问ContactsProvider,必须请求以下权限:

对一个或多个表的读取权限READ_CONTACTS

1
<uses-permission android:name="android.permission.READ_CONTACTS">

对一个或多个表的写入权限WRITE_CONTACTS

1
<uses-permission android:name="android.permission.WRITE_CONTACTS">

如果需要访问用户的个人信息,还需要以下权限:

1
2
<uses-permission android:name="android.permission.READ_PROFILE">
<uses-permission android:name="android.permission.READ_PROFILE">

entity查询操作

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

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

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

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

查询实例可查看/packages/apps/Contacts/src/com/android/contacts/model/ContactLoader.java类中的查询方法,比如:loadContactEntity()

批量处理操作

批量操作

在ContactsProvider中插入、更新和删除多条数据的时候,应该尽量使用“批处理模式”去操作,ContentProvider为我们提供了一个辅助类ContentProviderOperation用来进行Provider的批处理操作,在ContactsProvider中是用的时候,需要创建ArrayList,每一条操作对应一个Operation,调用ContentResolver->applyBatch()方法进行实现批处理操作。
applyBatch()中通过单一事务执行所有操作,通过此方式修改绝不会使联系人存储区出现不一致问题。 此外,批量修改还有便于同时插入raw_contacts及其详细数据。

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

实例可查看/packages/apps/Contacts/src/com/android/contacts/ContactSaveService.java类中的方法createRawContact()

挂起点(YieldAllowed)

大量的批处理可能会阻断其他进程,导致糟糕的总体用户体验。
要将想执行的所有修改组织到尽可能少的单独列表中,同时防止它们阻断系统,则应为一项或多项操作设置挂起点
ContentProviderOperation.Builder->withYieldAllowed()方法用来添加挂起点

applyBatch()执行过程中遇到挂起点时,它会暂停其工作,让其他进程运行,并关闭当前事务。 当提供程序再次启动时,它会继续执行 ArrayList 中的下一项操作,并启动一个新的事务。

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

挂起点也是一个原子操作单元。两个挂起点之间所有访问的成功或失败都将以一个单元的形式出现。 如果您不设置任何挂起点,则最小的原子操作是整个批量操作。 如果您使用了挂起点,则可以防止操作降低系统性能,还可确保一部分操作是原子操作。

向后引用概念

当我们使用 ContentProviderOperation 来插入新的 raw contacts 和与它相关的 data 数据时,必须要将 data 数据的 raw_contacts_id 这一列关联到 raw contact 的RAW_CONTACT_ID 列上。然而,这个值在创建 ContentProviderOperation 对象时还没有,因为插入 raw contact 的操作还没有完成。为了解决这个问题,ContentProviderOperation.Builder 类提供了withValueBackReference()这个方法,它允许基于上一条操作的结果来插入或者修改数据。

批处理操作实现乐观并发控制

通过Intent访问数据

通过想Contacts应用发送Intent,可以做到间接访问联系人数据,Intent会执行Contacts应用的UI,用户可通过联系人UI执行相关的操作,其主要功能如下:

  • 从列表中选取一位联系人并将其返回给你的应用以执行进一步操作。

  • 编辑现有联系人的数据。

  • 为其任一帐户插入新raw_contact。

  • 删除联系人或联系人数据。

联系人Intent操作接口:

任务 操作 数据 MIME 类型 说明
从列表中选取一位联系人 ACTION_PICK 下列值之一:
Contacts.CONTENT_URI,显示联系人列表。
Phone.CONTENT_URI,显示raw_contact的电话号码列表。
StructuredPostal.CONTENT_URI,显示raw_contact的邮政地址列表。
Email.CONTENT_URI,显示raw_contact的电子邮件地址列表
未使用 显示raw_contact列表或一位raw_contact的数据列表,具体取决于您提供的内容 URI 类型。调用 startActivityForResult() 方法,该方法返回所选行的内容 URI。 该 URI 的形式为:追加有该行 LOOKUP_ID 的表的内容 URI。设备的联系人应用会在 Activity 的生命周期内将读取和写入权限授予给此内容 URI。如需了解更多详细信息,请参阅内容提供程序基础知识指南。
插入新raw_contact Insert.ACTION 不适用 RawContacts.CONTENT_TYPE,用于一组raw_contact的 MIME 类型。 显示设备“通讯录”应用的 Add Contact 屏幕。系统会显示您添加到 Intent 中的 Extra 值。如果是随 startActivityForResult() 发送,系统会将新添加的raw_contact的内容 URI 传回给 Activity 的 onActivityResult() 回调方法并作为后者 Intent 参数的“data”字段。要获取该值,请调用 getData()。
编辑联系人 ACTION_EDIT 联系人的 CONTENT_LOOKUP_URI. 编辑界面允许用户去修改所有和这个联系人相关的数据。 Contacts.CONTENT_ITEM_TYPE, a single contact. 调出联系人应用的编辑界面,添加在 intent 中的数据可以显示出来。点击 完成 来保存修改后,你的 activity 就会被显示出来。
显示一个同样可以添加数据的选取器。 ACTION_INSERT_OR_EDIT 不适用 CONTENT_ITEM_TYPE 此 Intent 始终显示“通讯录”应用的选取器屏幕。用户可以选取要编辑的联系人,或添加新联系人。 根据用户的选择,系统会显示编辑屏幕或添加屏幕,还会显示您使用 Intent 传递的 Extra 数据。如果您的应用显示电子邮件或电话号码等联系人数据,请使用此 Intent 来允许用户向现有联系人添加数据。

注:不需要通过此 Intent 的 Extra 发送姓名值,因为用户总是会选取现有姓名或添加新姓名。 此外,如果您发送姓名,并且用户选择执行编辑操作,则联系人应用将显示您发送的姓名,该姓名将覆盖以前的值。 如果用户未注意这一情况便保存了编辑,原有值将会丢失。

设备的联系人应用不允许使用 Intent 删除raw_contact或其任何数据。 因此,要删除raw_contact,可以使用 ContentResolver.delete() 或 ContentProviderOperation.newDelete()。

数据完整性规则

Contacts Provider 为了保证数据的完整性定义了一些规则。当用户修改联系人数据时,你要确保所做的操作都能够满足这些规则。下面是几个比较重要的规则:

  • 务必为您添加的每个 ContactsContract.RawContacts 行添加一个 ContactsContract.CommonDataKinds.StructuredName 行。

    如果 ContactsContract.Data 表中的 ContactsContract.RawContacts 行没有 ContactsContract.CommonDataKinds.StructuredName 行,可能会在聚合时引发问题。

  • 务必将新 ContactsContract.Data 行链接到其父 ContactsContract.RawContacts 行。

    如果 ContactsContract.Data 行未链接到 ContactsContract.RawContacts,则其在设备的联系人应用中将处于不可见状态,而且这可能会导致同步适配器出现问题。

  • 请仅更改您拥有的那些raw_contact的数据。

    请切记,ContactsProvider所管理的数据通常来自多个不同帐户类型/在线服务。 您需要确保您的应用仅修改或删除归您所有的行的数据,并且仅通过您控制的帐户类型和帐户名称插入数据。

  • 务必使用在 ContactsContract 及其子类中为权限、内容 URI、URI 路径、列名称、MIME 类型以及 TYPE 值定义的常量。

    使用这些常量有助于您避免错误。如有任何常量被弃用,您还会从编译器警告收到通知。

ContactsProvider同步适配器

ContactsProvider专门设计用于处理设备与在线服务之间的联系人数据同步。 借助同步功能,用户可以将现有数据下载到新设备,以及将现有数据上传到新帐户。 此外,同步还能确保用户掌握最新数据,无需考虑数据增加和更改的来源。 同步的另一个优点是,即使设备未连接网络,联系人数据同样可用。

虽然您可以通过各种方式实现同步,不过 Android 系统提供了一个插件同步框架,可自动化完成下列任务:

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

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

同步适配器类和文件

您需要将同步适配器作为 AbstractThreadedSyncAdapter 的子类进行实现,并作为 Android 应用的一部分进行安装。系统通过您的应用清单文件中的元素以及由清单文件指向的一个特殊 XML 文件了解有关同步适配器的信息。 该 XML 文件定义在线服务的帐户类型和内容提供程序的权限,它们共同对适配器进行唯一标识。 用户为同步适配器的帐户类型添加一个帐户,并为与同步适配器同步的内容提供程序启用同步后,同步适配器才会激活。 激活后,系统将开始管理适配器,并在必要时调用它,以在内容提供程序与服务器之间同步数据。

注:将帐户类型用作同步适配器标识的一部分让系统可以发现从同一组织访问不同服务的同步适配器,并将它们组合在一起。 例如,Google 在线服务的同步适配器都具有相同的帐户类型 com.google。 当用户向其设备添加 Google 帐户时,已安装的所有 Google 服务同步适配器将一起列出;列出的每个同步适配器都与设备上不同的内容提供程序同步。

大多数服务都要求用户验证身份后才能访问数据,为此,Android 系统提供了一个身份验证框架,该框架与同步适配器框架类似,并且经常与其联用。 该身份验证框架使用的插件身份验证器是 AbstractAccountAuthenticator 的子类。 身份验证器通过下列步骤验证用户的身份:

  • 收集用户名、用户密码或类似信息(用户的凭据)。
  • 将凭据发送给服务
  • 检查服务的回复。

如果服务接受了凭据,身份验证器便可存储凭据以供日后使用。 由于插件身份验证器框架的存在,AccountManager 可以提供对身份验证器支持并选择公开的任何身份验证令牌(例如 OAuth2 身份验证令牌)的访问。

尽管身份验证并非必需,但大多数联系人服务都会使用它。 不过,您不一定要使用 Android 身份验证框架进行身份验证。

同步适配器实现

要为ContactsProvider实现同步适配器,您首先要创建一个包含以下内容的 Android 应用:

1.一个 Service 组件,用于响应系统发出的绑定到同步适配器的请求。

当系统想要运行同步时,它会调用服务的 onBind() 方法,为同步适配器获取一个 IBinder。这样,系统便可跨进程调用适配器的方法。
在示例同步适配器示例应用中,该服务的类名是 com.example.android.samplesync.syncadapter.SyncService。

2.作为 AbstractThreadedSyncAdapter 具体子类实现的实际同步适配器。

此类的作用是从服务器下载数据、从设备上传数据以及解决冲突。 适配器的主要工作是在方法 onPerformSync() 中完成的。 必须将此类实例化为单一实例。
在示例同步适配器示例应用中,同步适配器是在 com.example.android.samplesync.syncadapter.SyncAdapter 类中定义的。

3.Application 的子类。

此类充当同步适配器单一实例的工厂。使用 onCreate() 方法实例化同步适配器,并提供一个静态“getter”方法,使单一实例返回同步适配器服务的 onBind() 方法。

4.可选:一个 Service 组件,用于响应系统发出的用户身份验证请求。

AccountManager 会启动此服务以开始身份验证流程。 该服务的 onCreate() 方法会将一个身份验证器对象实例化。 当系统想要对应用同步适配器的用户帐户进行身份验证时,它会调用该服务的 onBind() 方法,为该身份验证器获取一个 IBinder。 这样,系统便可跨进程调用身份验证器的方法。
在示例同步适配器示例应用中,该服务的类名是 com.example.android.samplesync.authenticator.AuthenticationService。

5.可选:一个用于处理身份验证请求的 AbstractAccountAuthenticator 具体子类。

AccountManager 就是调用此类所提供的方法向服务器验证用户的凭据。 详细的身份验证过程会因服务器所采用技术的不同而有很大差异。 您应该参阅服务器软件的文档,了解有关身份验证的更多信息。
在示例同步适配器示例应用中,身份验证器是在 com.example.android.samplesync.authenticator.Authenticator 类中定义的。

6.用于定义系统同步适配器和身份验证器的 XML 文件。

之前描述的同步适配器和身份验证器服务组件都是在应用清单文件中的 元素内定义的。 这些元素包含以下用于向系统提供特定数据的 子元素:

(1)同步适配器服务的 元素指向 XML 文件 res/xml/syncadapter.xml。而该文件则指定将与ContactsProvider同步的网络服务的 URI,以及指定该 Web 服务的帐户类型。
(2)可选:身份验证器的 元素指向 XML 文件 res/xml/authenticator.xml。而该文件则指定此身份验证器所支持的帐户类型,以及指定身份验证过程中出现的 UI 资源。 在此元素中指定的帐户类型必须与为同步适配器指定的帐户类型相同。

社交流数据

android.provider.ContactsContract.StreamItems 表和 android.provider.ContactsContract.StreamItemPhotos 表管理来自社交网络的传入数据。 您可以编写一个同步适配器,用其将您自己社交网络中的流数据添加到这些表中,也可以从这些表读取流数据并将其显示在您的自有应用中,或者同时采用这两种方法。 利用这些功能,可以将您的社交网络服务和应用集成到 Android 的社交网络体验之中。

社交流文本

流项目始终与raw_contact关联。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID 链接到raw_contact的 _ID 值。 raw_contact的帐户类型和帐户名称也存储在流项目行中。

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

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
必备。与该流项目关联的raw_contact对应的用户帐户类型。 请记得在插入流项目时设置此值。

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必备。与该流项目关联的raw_contact对应的用户帐户名称。 请记得在插入流项目时设置此值。
标识符列

必备。您必须在插入流项目时插入下列标识符列:
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:此流项目关联的raw_contact的 android.provider.BaseColumns#_ID 值。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS

可选。存储可在流项目开头显示的摘要信息。
android.provider.ContactsContract.StreamItemsColumns#TEXT
流项目的文本,或为项目来源发布的内容,或是对生成流项目的某项操作的描述。 此列可包含可由 fromHtml() 渲染的任何格式设置和嵌入式资源图像。 提供程序可能会截断或省略较长内容,但它会尽力避免破坏标记。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
一个包含流项目插入时间或更新时间的文本字符串,以从公元纪年开始计算的毫秒数形式表示。 此列由插入或更新流项目的应用负责维护;ContactsProvider不会自动对其进行维护。
要显示您的流项目的标识信息,请使用 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 列(一个二进制大型对象)。
照片的二进制表示,为便于存储和显示,由提供程序调整了尺寸。 此列可用于向后兼容使用它来存储照片的旧版本ContactsProvider。 不过,在当前版本中,您不应使用此列来存储照片, 而应使用 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID 或 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(下文对两者都做了描述)将照片存储在一个文件内。 此列现在包含可用于读取的照片缩略图。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
raw_contact照片的数字标识符。将此值追加到常量 DisplayPhoto.CONTENT_URI,获取指向单一照片文件的内容 URI,然后调用 openAssetFileDescriptor() 来获取照片文件的句柄。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
一个内容 URI,直接指向此行所表示的照片的照片文件。 通过此 URI 调用 openAssetFileDescriptor() 以获得照片文件的句柄。
使用社交流表
这些表的工作方式与ContactsProvider中的其他主表基本相同,不同的是:

这些表需要额外的访问权限。要读取它们的数据,您的应用必须具有 android.Manifest.permission#READ_SOCIAL_STREAM 权限。 要修改它们,您的应用必须具有 android.Manifest.permission#WRITE_SOCIAL_STREAM 权限。
对于 android.provider.ContactsContract.StreamItems 表,为每一位raw_contact存储的行数有限。 一旦达到该限制,ContactsProvider即会自动删除 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 的一个子表,其中包含某个流项目的照片行。

社交流交互

通过将ContactsProvider管理的社交流数据与设备的联系人应用相结合,可以在您的社交网络系统与现有联系人之间建立起有效的连接。 这种结合实现了下列功能:

您可以通过同步适配器让您的社交网络服务与ContactsProvider同步,检索用户联系人的近期 Activity,并将其存储在 android.provider.ContactsContract.StreamItems 表和 android.provider.ContactsContract.StreamItemPhotos 表中,以供日后使用。
除了定期同步外,您还可以在用户选择某位联系人进行查看时触发您的同步适配器以检索更多数据。 这样,您的同步适配器便可检索该联系人的高分辨率照片和最近流项目。
通过在设备的联系人应用以及ContactsProvider中注册通知功能,您可以在用户查看联系人时收到一个 Intent,并在那时通过您的服务更新联系人的状态。 与通过同步适配器执行完全同步相比,此方法可能更快速,占用的带宽也更少。
用户可以在查看设备联系人应用中的联系人时,将其添加到您的社交网络服务。 您可以通过“邀请联系人”功能实现此目的,而该功能则是通过将 Activity 与 XML 文件结合使用来实现的,前者将现有联系人添加到您的社交网络,后者为设备的联系人应用以及ContactsProvider提供有关您的应用的详细信息。
流项目与ContactsProvider的定期同步与其他同步相同。 如需了解有关同步的更多信息,请参阅 ContactsProvider同步适配器部分。接下来的两节介绍如何注册通知和邀请联系人。

通过注册处理社交网络查看
要注册您的同步适配器,以便在用户查看由您的同步适配器管理的联系人时收到通知,请执行以下步骤:

在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 如果您已有该文件,可跳过此步骤。
在该文件中添加元素

1
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">

如果该元素已存在,可跳过此步骤。
要注册一项服务,以便在用户于设备的联系人应用中打开某位联系人的详细信息页面时通知该服务,请为该元素添加 viewContactNotifyService=”serviceclass” 属性,其中 serviceclass 是该服务的完全限定类名,应由该服务接收来自设备联系人应用的 Intent。 对于这个通知程序服务,请使用一个扩展 IntentService 的类,以让该服务能够接收 Intent。 传入 Intent 中的数据包含用户点击的raw_contact的内容 URI。 您可以通过通知程序服务绑定到您的同步适配器,然后调用同步适配器来更新raw_contact的数据。
要注册需要在用户点击流项目或照片(或同时点击这两者)时调用的 Activity,请执行以下步骤:

在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 如果您已有该文件,可跳过此步骤。
在该文件中添加元素

1
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">

如果该元素已存在,可跳过此步骤。
要注册某个 Activity,以处理用户在设备联系人应用中点击某个流项目的操作,请为该元素添加 viewStreamItemActivity=”activityclass” 属性,其中 activityclass 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。
要注册某个 Activity,以处理用户在设备联系人应用中点击某个流照片的操作,请为该元素添加 viewStreamItemPhotoActivity=”activityclass” 属性,其中 activityclass 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。

元素部分对 元素做了更详尽的描述。

传入 Intent 包含用户点击的项目或照片的内容 URI。 要让文本项目和照片具有独立的 Activity,请在同一文件中使用这两个属性。

与您的社交网络服务交互
用户不必为了邀请联系人到您的社交网络网站而离开设备的联系人应用。 取而代之是,您可以让设备的联系人应用发送一个 Intent,将联系人 邀请到您的 Activity 之一。要设置此功能,请执行以下步骤:

在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 如果您已有该文件,可跳过此步骤。
在该文件中添加元素

1
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">

如果该元素已存在,可跳过此步骤。
添加以下属性:

1
2
inviteContactActivity="activityclass"
inviteContactActionLabel="@string/invite_action_label"

activityclass 值是应该接收该 Intent 的 Activity 的完全限定类名。 invite_action_label 值是一个文本字符串,将显示在设备联系人应用的 Add Connection 菜单中。
注:ContactsSource 是 ContactsAccountType 的一个已弃用的标记名称。

contacts.xml 引用
文件 contacts.xml 包含一些 XML 元素,这些元素控制您的同步适配器和应用与联系人应用及ContactsProvider的交互。 下文对这些元素做了描述。

元素

元素控制您的应用与联系人应用的交互。 它采用了以下语法:

1
2
3
4
5
6
7
8
9
<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

可包含:

说明:

声明 Android 组件和 UI 标签,让用户能够邀请他们的一位联系人加入社交网络,在他们的某个社交网络流更新时通知用户,以及执行其他操作。

请注意,对 的属性而言,属性前缀 android: 并非必需的。

属性:

inviteContactActivity
您的应用中某个 Activity 的完全限定类名,您想要在用户于设备的联系人应用中选择 Add connection 时激活该 Activity。
inviteContactActionLabel
Add connection 菜单中为 inviteContactActivity 中指定的 Activity 显示的文本字符串。 例如,您可以使用字符串“Follow in my network”。您可以为此标签使用字符串资源标识符。
viewContactNotifyService
您的应用中某项服务的完全限定类名,当用户查看联系人时,应由该服务接收通知。 此通知由设备的联系人应用发送;您的应用可以根据通知将数据密集型操作推迟到必要时再执行。 例如,您的应用对此通知的响应可以是:读入并显示联系人的高分辨率照片和最近的社交流项目。 社交流交互部分对此功能做了更详尽的描述。 您可以在 SampleSyncAdapter 示例应用的 NotifierService.java 文件中查看通知服务的示例。
viewGroupActivity
您的应用中某个可显示组信息的 Activity 的完全限定类名。 当用户点击设备联系人应用中的组标签时,将显示此 Activity 的 UI。
viewGroupActionLabel
联系人应用为某个 UI 控件显示的标签,用户可通过该控件查看您的应用中的组。
例如,如果您在设备上安装了 Google+ 应用,并将 Google+ 与联系人应用同步,就会看到 Google+ 圈子以组的形式出现在您的联系人应用的 Groups 选项卡内。 如果您点击某个 Google+ 圈子,就会看到该圈子内的联系人以“组”的形式列出。 在该显示页面的顶部,您会看到一个 Google+ 图标;如果您点击它,控制权将切换给 Google+ 应用。 “通讯录”应用以 Google+ 图标作为 viewGroupActionLabel 的值,通过 viewGroupActivity 来实现此目的。

允许使用字符串资源标识符作为该属性的值。

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

元素

元素控制您的应用的自定义数据行在联系人应用 UI 中的显示。 它采用了以下语法:

1
2
3
4
5
<ContactsDataKind
android:mimeType="MIMEtype"
android:icon="icon_resources"
android:summaryColumn="column_name"
android:detailColumn="column_name">

包含它的文件:


说明:

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

属性:

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

其他ContactsProvider功能

联系人组

ContactsProvider可以选择性地为相关联系人集合添加组数据标签。 如果与某个用户帐户关联的服务器想要维护组,则与该帐户的帐户类型对应的同步适配器应在ContactsProvider与服务器之间传送组数据。 当用户向服务器添加一个新联系人,然后将该联系人放入一个新组时,同步适配器必须将这个新组添加到 ContactsContract.Groups 表中。 raw_contact所属的一个或多个组使用 ContactsContract.CommonDataKinds.GroupMembership MIME 类型存储在 ContactsContract.Data 表内。

如果您设计的同步适配器会将服务器中的raw_contact数据添加到ContactsProvider,并且您不使用组,则需要指示提供程序让您的数据可见。 在用户向设备添加帐户时执行的代码中,更新ContactsProvider为该帐户添加的 ContactsContract.Settings 行。 在该行中,将 Settings.UNGROUPED_VISIBLE 列的值设置为 1。执行此操作后,即使您不使用组,ContactsProvider也会让您的联系人数据始终可见。

联系人照片

ContactsContract.Data 表通过 MIME 类型 Photo.CONTENT_ITEM_TYPE 以行的形式存储照片。该行的 CONTACT_ID 列链接到其所属raw_contact的 _ID 列。 ContactsContract.Contacts.Photo 类定义了一个 ContactsContract.Contacts 子表,其中包含联系人主要照片(联系人的主要raw_contact的主要照片)的照片信息。 同样, ContactsContract.RawContacts.DisplayPhoto 类定义了一个 ContactsContract.RawContacts 子表,其中包含raw_contact主要照片的照片信息。

ContactsContract.Contacts.Photo 和 ContactsContract.RawContacts.DisplayPhoto 参考文档包含检索照片信息的示例。 并没有可用来检索raw_contact主要缩略图的实用类,但您可以向 ContactsContract.Data 表发送查询,从而通过选定raw_contact的 _ID、 Photo.CONTENT_ITEM_TYPE 以及 IS_PRIMARY 列,找到raw_contact的主要照片行。

联系人的社交流数据也可能包含照片。这些照片存储在 android.provider.ContactsContract.StreamItemPhotos 表中,社交流照片部分对该表做了更详尽的描述。