Android官方文档—APP组件(Content Providers)(Contacts Provider)

通讯录内容提供者

Contacts Provider是一个功能强大且灵活的Android组件,用于管理设备的人员数据中​​央存储库。联系人提供程序是您在设备的联系人应用程序中看到的数据源,您还可以在自己的应用程序中访问其数据,并在设备和在线服务之间传输数据。提供商可以容纳各种数据源,并尝试为每个人管理尽可能多的数据,结果是组织结构复杂。因此,提供者的API包括一组广泛的合同类和接口,便于数据检索和修改。

本指南介绍以下内容:

  • 基本的提供者结构。
  • 如何从提供程序检索数据。
  • 如何修改提供程序中的数据。
  • 如何编写同步适配器以将数据从服务器同步到Contacts Provider。

本指南假设您了解Android内容提供商的基础知识。要了解有关Android内容提供商的更多信息,请阅读内容提供商基础知识指南。 Sample Sync Adapter示例应用程序是使用同步适配器在Contacts Provider和Google Web Services托管的示例应用程序之间传输数据的示例。

通讯录供应商架构


Contacts Provider是Android内容提供商组件。它维护有关一个人的三种类型的数据,每种数据对应于提供者提供的表,如图1所示:

图1. Contacts Provider表结构。

这三个表通常用其合同类的名称来表示。这些类定义了表使用的内容URI,列名和列值的常量:

ContactsContract.Contacts表

代表不同人的行,基于原始联系行的聚合。

ContactsContract.RawContacts表

包含人员数据摘要的行,特定于用户帐户和类型。

ContactsContract.Data表

行包含原始联系人的详细信息,例如电子邮件地址或电话号码。

ContactsContract中由合同类表示的其他表是Contacts Provider用于管理其操作或支持设备的联系人或电话应用程序中的特定功能的辅助表。

原始联系人


原始联系人表示来自单个帐户类型和帐户名称的人员数据。由于联系人提供程序允许多个联机服务作为人员的数据源,因此联系人提供程序允许同一个人使用多个原始联系人。多个原始联系人还允许用户组合来自同一帐户类型的多个帐户的人员数据。

原始联系人的大多数数据不存储在ContactsContract.RawContacts表中。相反,它存储在ContactsContract.Data表中的一行或多行中。每个数据行都有一列Data.RAW_CONTACT_ID,其中包含其父ContactsContract.RawContacts行的RawContacts._ID值。

重要的原始数据列

ContactsContract.RawContacts表中的重要列列在表1中。请阅读表后面的注释:

表1.重要的原始接触列。

列名用法备注
ACCOUNT_NAME作为此原始联系人来源的帐户类型的帐户名称。例如,Google帐户的帐户名称是设备所有者的Gmail地址之一。有关详细信息,请参阅ACCOUNT_TYPE的下一个条目。此名称的格式特定于其帐户类型。它不一定是电子邮件地址。
ACCOUNT_TYPE作为此原始联系人来源的帐户类型。例如,Google帐户的帐户类型是com.google。始终使用您拥有或控制的域的域标识符限定您的帐户类型。这将确保您的帐户类型是唯一的。提供联系人数据的帐户类型通常具有与联系人提供程序同步的关联同步适配器。
DELETED原始联系人的“已删除”标志。此标志允许联系人提供程序在内部维护该行,直到同步适配器能够从其服务器中删除该行,然后最终从存储库中删除该行。

注意

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

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

例如,如果您希望自己的应用使用域com.example.dataservice维护基于Web的服务的联系人数据,并且用户的服务帐户是becky.sharp@dataservice.example.com,则用户必须先添加在您的应用添加原始联系人行之前,帐户“type”(com.example.dataservice)和帐户“name”(becky.smart@dataservice.example.com)。您可以在文档中向用户解释此要求,也可以提示用户添加类型和名称,或两者都添加。帐户类型和帐户名称将在下一节中详细介绍。

原始联系人数据的来源

要了解原始联系人的工作原理,请考虑在其设备上定义了以下三个用户帐户的用户“Emily Dickinson”:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Twitter account "belle_of_amherst"

此用户已在“帐户”设置中为所有这三个帐户启用了“同步联系人”。

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

由于这项工作,Contacts Provider会创建三个原始联系人:

  1. “Thomas Higginson”的原始联系人与emily.dickinson@gmail.com相关联。用户帐户类型为Google。
  2. “Thomas Higginson”的第二个原始联系人与emilyd@gmail.com相关联。用户帐户类型也是Google。即使名称与以前的名称相同,也存在第二个原始联系人,因为该人员是为其他用户帐户添加的。
  3.  与“belle_of_amherst”相关的“Thomas Higginson”的第三个原始联系人。用户帐户类型是Twitter。

数据


如前所述,原始联系人的数据存储在ContactsContract.Data行中,该行链接到原始联系人的_ID值。这允许单个原始联系人具有相同类型数据的多个实例,例如电子邮件地址或电话号码。例如,如果emilyd@gmail.com的“Thomas Higginson”(Thomas Higginson的原始联系人行与Google帐户emilyd@gmail.com相关联)的家庭电子邮件地址为thigg@gmail.com,工作电子邮件地址为thomas.higginson@gmail.com,Contacts Provider存储两个电子邮件地址行,并将它们链接到原始联系人。

请注意,此单个表中存储了不同类型的数据。显示名称,电话号码,电子邮件,邮政地址,照片和网站详细信息行均可在ContactsContract.Data表中找到。为了帮助管理它,ContactsContract.Data表包含一些具有描述性名称的列,以及具有通用名称的其他列。无论行中的数据类型如何,描述性名称列的内容都具有相同的含义,而通用名称列的内容根据数据类型具有不同的含义。

描述性列名

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

RAW_CONTACT_ID

此数据的原始联系人的_ID列的值。

MIMETYPE

存储在此行中的数据类型,表示为自定义MIME类型。 Contacts Provider使用ContactsContract.CommonDataKinds子类中定义的MIME类型。这些MIME类型是开源的,可以与任何与Contacts Provider一起使用的应用程序或同步适配器使用。

IS_PRIMARY

如果原始联系人可以多次出现此类数据行,则IS_PRIMARY列会标记包含该类型主数据的数据行。例如,如果用户长按联系人的电话号码并选择“设置默认值”,则包含该号码的ContactsContract.Data行将其IS_PRIMARY列设置为非零值。

通用列名称

有15个名为DATA1到DATA15的通用列通常可用,还有另外四个通用列SYNC1到SYNC4,它们只能由同步适配器使用。无论行包含哪种数据类型,通用列名常量始终有效。

DATA1列已编制索引。联系人提供程序始终将此列用于提供程序期望的数据,该数据将是查询的最常见目标。例如,在电子邮件行中,此列包含实际的电子邮件地址。

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

特定于类型的列名称

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

例如,ContactsContract.CommonDataKinds.Email类为ContactsContract.Data行定义类型特定的列名常量,该行具有MIME类型Email.CONTENT_ITEM_TYPE。该类包含电子邮件地址列的常量ADDRESS。 ADDRESS的实际值是“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将原始联系人链接到“联系人”提供程序中的一个组的标识符。组是帐户类型和帐户名称的可选功能。它们在联系人组部分中有更详细的描述。

通讯录

联系人提供商将所有帐户类型和帐户名称中的原始联系人行组合在一起以形成联系人。这有助于显示和修改用户为人收集的所有数据。联系人提供程序管理新联系人行的创建,以及原始联系人与现有联系人行的聚合。应用程序和同步适配器都不允许添加联系人,联系行中的某些列是只读的。

注意:如果您尝试使用insert()将联系人添加到Contacts Provider,则会收到UnsupportedOperationException异常。如果您尝试更新列为“只读”的列,则忽略更新。

联系人提供商创建新联系人以响应添加与任何现有联系人不匹配的新原始联系人。如果现有原始联系人的数据以不再与之前附加的联系人匹配的方式更改,则提供程序也会执行此操作。如果应用程序或同步适配器创建与现有联系人匹配的新原始联系人,则新的原始联系人将聚合到现有联系人。

“联系人”提供程序将联系人行与其原始联系人行链接,并在“联系人”表中将联系人行的_ID列链接。原始联系人表ContactsContract.RawContacts的CONTACT_ID列包含与每个原始联系人行关联的联系人行的_ID值。

ContactsContract.Contacts表还有LOOKUP_KEY列,它是联系行的“永久”链接。由于联系人提供程序会自动维护联系人,因此可能会更改联系人行的_ID值以响应聚合或同步。即使发生这种情况,内容URI CONTENT_LOOKUP_URI结合联系人的LOOKUP_KEY仍将指向联系人行,因此您可以使用LOOKUP_KEY维护指向“最喜欢”联系人的链接,依此类推。此列具有自己的格式,与_ID列的格式无关。

图3显示了三个主要表格如何相互关联。

图3. Contacts,Raw Contacts和Details表关系。

来自同步适配器的数据


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

在Android中,同步适配器使用的Web服务由帐户类型标识。每个同步适配器使用一种帐户类型,但它可以支持该类型的多个帐户名称。帐户类型和帐户名称在原始联系人数据源中简要描述。以下定义提供了更多详细信息,并描述了帐户类型和名称与同步适配器和服务的关系。

帐户类型

标识用户已存储数据的服务。大多数情况下,用户必须使用该服务进行身份验证。例如,Google通讯录是一种帐户类型,由代码google.com标识。此值对应于AccountManager使用的帐户类型。

用户名

标识帐户类型的特定帐户或登录。 Google通讯录帐户与Google帐户相同,后者的电子邮件地址为帐户名称。其他服务可能使用单字用户名或数字ID。

帐户类型不必是唯一的。用户可以配置多个Google通讯录帐户并将其数据下载到联系人提供商;如果用户有一组个人帐户名称的个人联系人,另一组用于工作,则可能发生这种情况。帐户名称通常是唯一的。它们共同确定了联系人提供者和外部服务之间的特定数据流。

如果要将服务的数据传输到Contacts Provider,则需要编写自己的同步适配器。 “联系人提供程序同步适配器”一节中对此进行了更详细的描述。

图4显示了Contacts Provider如何适应人员数据流。在标有“同步适配器”的框中,每个适配器都标有其帐户类型。

图4. Contacts Provider数据流。

必需的权限


想要访问联系人提供程序的应用程序必须请求以下权限:

对一个或多个表的读访问权限

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表有一行包含设备用户的配置文件数据。此数据描述设备的用户而不是用户的一个联系人。配置文件联系人行链接到使用配置文件的每个系统的原始联系人行。每个配置文件原始联系人行可以有多个数据行。 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”。

联系人提供商元数据


联系人提供程序管理跟踪存储库中联系人数据状态的数据。有关存储库的此元数据存储在各种位置,包括Raw Contacts,Data和Contacts表行,ContactsContract.Settings表和ContactsContract.SyncState表。下表显示了每个元数据的效果:

表3. Contacts Provider中的元数据

意义
ContactsContract.RawContactsDIRTY“0” - 自上次同步后未更改。标记在设备上更改的原始联系人,并且必须同步回服务器。当Android应用程序更新行时,联系人提供程序会自动设置该值。
“1” - 自上次同步后更改,需要同步回服务器。修改原始联系人或数据表的同步适配器应始终将字符串CALLER_IS_SYNCADAPTER附加到他们使用的内容URI。这可以防止提供程序将行标记为脏。否则,同步适配器修改似乎是本地修改并发送到服务器,即使服务器是修改的源。
ContactsContract.RawContactsVERSION此行的版本号。每当行或其相关数据发生更改时,Contacts Provider会自动递增此值。
ContactsContract.DataDATA_VERSION此行的版本号。每当更改数据行时,Contacts Provider都会自动递增此值。
ContactsContract.RawContactsSOURCE_ID一个字符串值,用于唯一标识与创建该联系人的帐户的原始联系人。

当同步适配器创建新的原始联系人时,应将此列设置为服务器的原始联系人的唯一ID。当Android应用程序创建新的原始联系人时,应用程序应将此列留空。这表示同步适配器应在服务器上创建新的原始联系人,并获取SOURCE_ID的值。

特别是,每个帐户类型的源ID必须是唯一的,并且应该在同步中保持稳定:

  • 唯一:帐户的每个原始联系人都必须拥有自己的源ID。如果您不强制执行此操作,则会导致联系人应用程序出现问题。请注意,同一帐户类型的两个原始联系人可能具有相同的源ID。例如,帐户emily.dickinson@gmail.com的原始联系人“Thomas Higginson”被允许与帐户emilyd@gmail.com的原始联系人“Thomas Higginson”具有相同的源ID。
  • 稳定:源ID是原始联系人在线服务数据的永久部分。例如,如果用户从“应用程序”设置中清除“联系人存储”并重新同步,则还原的原始联系人应具有与以前相同的源ID。如果您不强制执行此操作,快捷方式将停止工作。
ContactsContract.GroupsGROUP_VISIBLE“0” - 此组中的联系人不应在Android应用程序UI中可见。此列用于与允许用户隐藏特定组中的联系人的服务器兼容。
“1” - 允许在应用程序UI中显示该组中的联系人。 
ContactsContract.SettingsUNGROUPED_VISIBLE“0” - 对于此帐户和帐户类型,不属于某个组的联系人对Android应用程序UI不可见。默认情况下,如果联系人的原始联系人都不属于某个组,则联系人不可见(原始联系人的组成员身份由ContactsContract.Data表中的一个或多个ContactsContract.CommonDataKinds.GroupMembership行指示)。通过在ContactsContract.Settings表行中为帐户类型和帐户设置此标志,可以强制不显示组的联系人。此标志的一个用途是显示不使用组的服务器的联系人。
“1” - 对于此帐户和帐户类型,应用程序UI可以看到不属于某个组的联系人。 
ContactsContract.SyncState(all)使用此表存储同步适配器的元数据。使用此表,您可以在设备上持久存储同步状态和其他与同步相关的数据。

联系提供者访问


本节介绍从“联系人提供程序”访问数据的准则,重点介绍以下内容:

  • 实体查询。
  • 批量修改。
  • 使用意图进行检索和修改。
  • 数据的完整性。

“联系人提供程序同步适配器”一节中还详细介绍了如何从同步适配器进行修改。

查询实体

由于Contacts Provider表是按层次结构组织的,因此检索行和链接到它的所有“子”行通常很有用。例如,要显示人员的所有信息,您可能需要检索单个ContactsContract.Contacts行的所有ContactsContract.RawContacts行,或单个ContactsContract.RawContacts行的所有ContactsContract.CommonDataKinds.Email行。为此,Contacts Provider提供实体构造,其作用类似于表之间的数据库连接。

实体就像一个由父表及其子表中的选定列组成的表。查询实体时,您将根据实体中可用的列提供投影和搜索条件。结果是包含的Cursor包含检索到的每个子表行的一行。例如,如果查询ContactsContract.Contacts.Entity以获取该名称的所有原始联系人的联系人姓名和所有ContactsContract.CommonDataKinds.Email行,则会返回一个Cursor,其中包含每行ContactsContract.CommonDataKinds.Email行的一行。

实体简化了查询。使用实体,您可以立即检索联系人或原始联系人的所有联系人数据,而不必首先查询父表以获取ID,然后必须使用该ID查询子表。此外,联系人提供程序在单个事务中处理针对实体的查询,这确保检索到的数据在内部是一致的。

注意:实体通常不包含父表和子表的所有列。如果您尝试使用不在实体的列名常量列表中的列名,您将获得异常。

以下代码段显示了如何检索联系人的所有原始联系人行。该代码段是一个较大的应用程序的一部分,它有两个活动,“主要”和“细节”。主要活动显示联系人行列表;当用户选择一个时,活动将其ID发送到详细活动。详细信息活动使用ContactsContract.Contacts.Entity显示与所选联系人关联的所有原始联系人的所有数据行。

此代码段取自“详细信息”activity:

...
    /*
     * 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.
}

加载完成后,LoaderManager调用onLoadFinished()的回调。此方法的一个传入参数是带有查询结果的Cursor。在您自己的应用程序中,您可以从此Cursor获取数据以显示它或进一步使用它。

批量修改

只要有可能,您应该通过创建ContentProviderOperation对象的ArrayList并调用applyBatch(),以“批处理模式”在Contacts Provider中插入,更新和删除数据。由于Contacts Provider在单个事务中执行applyBatch()中的所有操作,因此您的修改永远不会使联系人存储库处于不一致状态。批量修改还有助于同时插入原始联系人及其详细数据。

注意:要修改单个原始联系人,请考虑向设备的联系人应用程序发送意图,而不是在应用程序中处理修改。在使用意图检索和修改一节中更详细地描述了这样做。

屈服点

包含大量操作的批量修改可能会阻止其他进程,从而导致整体用户体验不佳。要在尽可能少的单独列表中组织要执行的所有修改,同时防止它们阻止系统,您应该为一个或多个操作设置屈服点。屈服点是ContentProviderOperation对象,其isYieldAllowed()值设置为true。当联系人提供程序遇到屈服点时,它会暂停其工作以让其他进程运行并关闭当前事务。当提供程序再次启动时,它将继续执行ArrayList中的下一个操作并启动新事务。

服点确实会导致每次调用applyBatch()时有多个事务。因此,您应为一组相关行的最后一个操作设置屈服点。例如,您应该为添加原始联系人行及其关联数据行的集合中的最后一个操作设置屈服点,或者为与单个联系人相关的一组行设置最后一个操作。

屈服点也是原子操作的单位。两个屈服点之间的所有访问将作为单个单元成功或失败。如果未设置任何屈服点,则最小的原子操作是整批操作。如果使用屈服点,则可以防止操作降低系统性能,同时确保操作子集是原子的。

修改后引用

当您将新的原始联系人行及其关联的数据行作为一组ContentProviderOperation对象插入时,必须通过将原始联系人的_ID值作为RAW_CONTACT_ID值插入,将数据行链接到原始联系人行。但是,当您为数据行创建ContentProviderOperation时,此值不可用,因为您尚未为原始联系人行应用ContentProviderOperation。要解决此问题,ContentProviderOperation.Builder类具有withValueBackReference()方法。此方法允许您插入或修改具有上一操作结果的列。

withValueBackReference()方法有两个参数:

key

键值对的关键。此参数的值应该是您正在修改的表中列的名称。

previousResult

来自applyBatch()的ContentProviderResult对象数组中的值的从0开始的索引。在应用批处理操作时,每个操作的结果都存储在一个中间结果数组中。 previousResult值是这些结果之一的索引,使用键值检索和存储。这允许您插入新的原始联系人记录并获取其_ID值,然后在添加ContactsContract.Data行时对该值进行“后向引用”。

第一次调用applyBatch()时会创建整个结果数组,其大小等于您提供的ContentProviderOperation对象的ArrayList的大小。但是,结果数组中的所有元素都设置为null,如果尝试对尚未应用的操作的结果进行反向引用,则withValueBackReference()会抛出异常。

以下代码段显示了如何批量插入新的原始联系人和数据。它们包括建立屈服点并使用反向引用的代码。这些片段是createContacEntry()方法的扩展版本,该方法是Contact Manager示例应用程序中ContactAdder类的一部分。

第一个代码段从UI检索联系人数据。此时,用户已经选择了应添加新原始联系人的帐户。

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

    int phoneType = mContactPhoneTypes.get(
            mContactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = mContactEmailTypes.get(
            mContactEmailTypeSpinner.getSelectedItemPosition());

下一个代码段创建一个操作,将原始联系人行插入ContactsContract.RawContacts表:

   /*
     * 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, mSelectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.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(),该对象设置屈服点:

 // 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()的调用,该调用插入了新的原始联系人和数据行。

// Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
            mSelectedAccount.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:

/*
 * 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
    mRawContactID = 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, mRawContactID);

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

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类型和数据值,而可以与putExtra()一起使用的extras值列在ContactsContract.Intents.Insert的参考文档中:

表4.联系人提供者意图。

TaskActionDataMIME typeNotes
从列表中选择一个联系人ACTION_PICK

以下之一:

  • Contacts.CONTENT_URI,显示联系人列表。
  • Phone.CONTENT_URI,显示原始联系人的电话号码列表。
  • StructuredPostal.CONTENT_URI,显示原始联系人的邮政地址列表。
  • Email.CONTENT_URI,显示原始联系人的电子邮件地址列表。
Not used显示原始联系人列表或原始联系人的数据列表,具体取决于您提供的内容URI类型。

调用startActivityForResult(),它返回所选行的内容URI。 URI的形式是表的内容URI,其中附加了行的LOOKUP_ID。设备的联系人应用程序在活动期间委托对此内容URI的读写权限。有关更多详细信息,请参阅Content Provider Basics指南。
插入新的原始联系人Insert.ACTIONN/ARawContacts.CONTENT_TYPE,一组原始联系人的MIME类型。显示设备的联系人应用程序的“添加联系人”屏幕。将显示您添加到意图中的额外值。如果使用startActivityForResult()发送,则新添加的原始联系人的内容URI将在“数据”字段中的Intent参数中传递回活动的onActivityResult()回调方法。要获取该值,请调用getData()。
编辑联系人ACTION_EDITCONTENT_LOOKUP_URI为联系人。编辑器活动将允许用户编辑与此联系人关联的任何数据。Contacts.CONTENT_ITEM_TYPE,单个联系人。显示联系人应用程序中的编辑联系人屏幕。将显示您添加到意图中的额外值。当用户单击“完成”以保存编辑时,您的活动将返回到前台。
显示也可以添加数据的选择器。ACTION_INSERT_OR_EDITN/ACONTENT_ITEM_TYPE

此意图始终显示联系人应用程序的选择器屏幕。用户可以选择要编辑的联系人,也可以添加新联系人。根据用户的选择,将显示编辑或添加屏幕,并显示您在意图中传递的额外数据。如果您的应用显示联系人数据(如电子邮件或电话号码),请使用此意图允许用户将数据添加到现有联系人。

注意:无需在此intent的附加内容中发送名称值,因为用户始终选择现有名称或添加新名称。此外,如果您发送名称,并且用户选择进行编辑,则联系人应用程序将显示您发送的名称,覆盖以前的值。如果用户没有注意到并保存编辑,则旧值将丢失。

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

以下代码段显示了如何构造和发送插入新原始联系人和数据的intent:

// Gets values from the UI
String name = mContactNameEditText.getText().toString();
String phone = mContactPhoneEditText.getText().toString();
String email = mContactEmailEditText.getText().toString();

String company = mCompanyName.getText().toString();
String jobtitle = mJobTitle.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, mSelectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());

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

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

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

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

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

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

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

数据的完整性

由于联系人存储库包含用户期望正确且最新的重要且敏感的数据,因此联系人提供程序具有明确定义的数据完整性规则。修改联系人数据时,您有责任遵守这些规则。这里列出了重要的规则:

始终为您添加的每个ContactsContract.RawContacts行添加ContactsContract.CommonDataKinds.StructuredName行。

在ContactsContract.Data表中没有ContactsContract.CommonDataKinds.StructuredName行的ContactsContract.RawContacts行可能会在聚合期间导致问题。

始终将新的ContactsContract.Data行链接到其父ContactsContract.RawContacts行。

未链接到ContactsContract.RawContacts的ContactsContract.Data行将在设备的联系人应用程序中不可见,并且可能会导致同步适配器出现问题。

仅为您拥有的原始联系人更改数据。

请记住,联系人提供商通常管理来自多种不同帐户类型/在线服务的数据。您需要确保应用程序仅修改或删除属于您的行的数据,并且仅插入具有您控制的帐户类型和名称的数据。

始终使用ContactsContract及其子类中定义的常量来获取权限,内容URI,URI路径,列名,MIME类型和TYPE值。

使用这些常量可以帮助您避免错误。如果不推荐任何常量,也会收到编译器警告通知。

自定义数据行

通过创建和使用自己的自定义MIME类型,您可以在ContactsContract.Data表中插入,编辑,删除和检索自己的数据行。您的行仅限于使用ContactsContract.DataColumns中定义的列,但您可以将自己的特定于类型的列名称映射到默认列名称。在设备的联系人应用程序中,将显示行的数据,但无法编辑或删除,用户无法添加其他数据。要允许用户修改自定义数据行,您必须在自己的应用程序中提供编辑器活动。

显示自定义数据,请提供包含<ContactsAccountType>元素及其一个或多个<ContactsDataKind>子元素的contacts.xml文件。这在<ContactsDataKind>元素部分中有更详细的描述。

要了解有关自定义MIME类型的更多信息,请阅读“创建内容提供商”指南。

联系人提供商同步适配器


联系人提供程序专门用于处理设备和在线服务之间的联系人数据同步。这允许用户将现有数据下载到新设备并将现有数据上载到新帐户。无论添加和更改的来源如何,同步还可确保用户掌握最新数据。同步的另一个优点是,即使设备未连接到网络,它也可以使联系人数据可用。

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

  • 检查网络可用性。
  • 根据用户首选项调度和执行同步。
  • 重新启动已停止的同步。

要使用此框架,请提供同步适配器插件。每个同步适配器对于服务和内容提供商都是唯一的,但可以处理同一服务的多个帐户名。该框架还允许同一服务和提供者使用多个同步适配器。

同步适配器类和文件

您将同步适配器实现为AbstractThreadedSyncAdapter的子类,并将其安装为Android应用程序的一部分。系统从应用程序清单中的元素以及清单指向的特殊XML文件中了解同步适配器。 XML文件定义在线服务的帐户类型和内容提供者的权限,它们一起唯一地标识适配器。在用户为同步适配器的帐户类型添加帐户并为同步适配器同步的内容提供程序启用同步之前,同步适配器不会变为活动状态。此时,系统开始管理适配器,根据需要调用它以在内容提供者和服务器之间进行同步。

注意:使用帐户类型作为同步适配器标识的一部分,系统可以检测同一组合的同步适配器并将其组合在一起。例如,Google在线服务的同步适配器都具有相同的帐户类型com.google。当用户将Google帐户添加到他们的设备时,所有已安装的Google服务同步适配器都会列在一起;列出的每个同步适配器与设备上的其他内容提供程序同步。

由于大多数服务要求用户在访问数据之前验证其身份,因此Android系统提供的身份验证框架与同步适配器框架类似,并且通常与其同时使用。身份验证框架使用插件身份验证器,它是AbstractAccountAuthenticator的子类。验证者通过以下步骤验证用户的身份:

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

如果服务接受凭据,则身份验证器可以存储凭据以供以后使用。由于插件验证器框架,AccountManager可以提供对验证者支持并选择公开的任何验证的访问权限,例如OAuth2 authtokens。

虽然不需要身份验证,但大多数联系人服务都使用它。但是,您不需要使用Android身份验证框架进行身份验证。

同步适配器实现

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

一个服务组件,响应来自系统的请求绑定到同步适配器。

当系统想要运行同步时,它会调用服务的onBind()方法来获取同步适配器的IBinder。这允许系统对适配器的方法执行跨进程调用。

在Sample Sync Adapter示例应用程序中,此服务的类名是com.example.android.samplesync.syncadapter.SyncService。

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

此类负责从服务器下载数据,从设备上传数据以及解决冲突。适配器的主要工作是在onPerformSync()方法中完成的。必须将此类实例化为单例。

在Sample Sync Adapter示例应用程序中,同步适配器在com.example.android.samplesync.syncadapter.SyncAdapter类中定义。

Application的子类。

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

可选:响应来自系统的用户身份验证请求的服务组件。

AccountManager启动此服务以开始身份验证过程。服务的onCreate()方法实例化一个authenticator对象。当系统想要为应用程序的同步适配器验证用户帐户时,它会调用服务的onBind()方法来获取验证者的IBinder。这允许系统对认证者的方法进行跨进程调用。

在Sample Sync Adapter示例应用程序中,此服务的类名是com.example.android.samplesync.authenticator.AuthenticationService。

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

此类提供AccountManager调用的方法,以便使用服务器验证用户的凭据。根据使用的服务器技术,身份验证过程的细节差异很大。您应参阅服务器软件的文档以了解有关身份验证的更多信息。

在Sample Sync Adapter示例应用程序中,验证程序在com.example.android.samplesync.authenticator.Authenticator类中定义。

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

前面描述的同步适配器和身份验证器服务组件在应用程序清单中的<service>元素中定义。这些元素包含<meta-data>子元素,它们为系统提供特定数据:

  • 步适配器服务的<meta-data>元素指向XML文件res / xml / syncadapter.xml。反过来,此文件指定将与联系人提供程序同步的Web服务的URI,以及Web服务的帐户类型。
  • 可选:验证者的<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

必要:用户与此流项目关联的原始联系人的帐户名称。请记住在插入流项目时设置此值。

标识符列

必要:插入流项时,必须插入以下标识符列:

  • 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#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)。

照片的二进制表示,由提供程序调整大小以进行存储和显示。此列可用于向后兼容用于存储照片的以前版本的Contacts Provider。但是,在当前版本中,您不应使用此列来存储照片。相反,使用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()以获取照片文件的句柄。

使用社交流表

这些表的工作方式与Contacts Provider中的其他主表相同,不同之处在于:

  • 这些表需要其他访问权限。要从中读取,您的应用程序必须具有android.Manifest.permission#READ_SOCIAL_STREAM权限。要修改它们,您的应用程序必须具有android.Manifest.permission#WRITE_SOCIAL_STREAM权限。
  • 对于android.provider.ContactsContract.StreamItems表,为每个原始联系人存储的行数是有限的。达到此限制后,Contacts 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的子表。

社交流互动

联系人提供商管理的社交流数据与设备的联系人应用程序一起,提供了一种将社交网络系统与现有联系人连接的强大方式。可以使用以下功能:

  • 通过使用同步适配器将您的社交网络服务同步到Contacts Provider,您可以检索用户联系人的最近活动,并将其存储在android.provider.ContactsContract.StreamItems和android.provider.ContactsContract.StreamItemPhotos表中供以后使用。
  • 除了常规同步之外,您还可以在用户选择要查看的联系人时触发同步适配器以检索其他数据。这允许您的同步适配器检索高分辨率照片和联系人的最新流项目。
  • 通过向设备的联系人应用程序和联系人提供程序注册通知,您可以在查看联系人时收到意图,并在此时更新服务中的联系人状态。与使用同步适配器进行完全同步相比,此方法可能更快并且使用更少的带宽。
  • 用户可以在查看设备联系人应用程序中的联系人时为您的社交网络服务添加联系人。您可以使用“邀请联系人”功能启用此功能,该功能通过将现有联系人添加到网络的活动和提供设备联系人应用程序的XML文件以及提供应用程序详细信息的联系人提供程序的组合启用。

流项目与Contacts Provider的定期同步与其他同步相同。有关同步的详细信息,请参阅“联系人提供程序同步适配器”一节。接下来的两节将介绍注册通知和邀请联系人。

注册以处理社交网络视图

注册同步适配器以在用户查看由同步适配器管理的联系人时接收通知:

  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. 要注册您的某个活动以处理用户单击设备的联系人应用程序中的流项目,请将属性viewStreamItemActivity =“activityclass”添加到元素,其中activityclass是应从其接收意图的活动的完全限定类名。设备的联系人应用程序。
  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值是应该接收intent的活动的完全限定类名。 invite_action_label值是一个文本字符串,显示在设备的联系人应用程序的“添加连接”菜单中。

注意:ContactsSource是ContactsAccountType的已弃用标记名称。

contacts.xml参考

文件的contacts.xml包含控制同步适配器和应用程序与联系人应用程序和联系人提供程序的交互的XML元素。以下各节介绍了这些元素。

<ContactsAccountType>元素

<ContactsAccountType>元素控制应用程序与联系人应用程序的交互。它具有以下语法:

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

包含在:

res/xml/contacts.xml

可以包含:

<ContactsDataKind>

描述:

声明Android组件和UI标签,允许用户邀请其中一个联系人加入社交网络,在其中一个社交网络流更新时通知用户,等等。

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

属性:

inviteContactActivity

当用户从设备的联系人应用程序中选择添加连接时,要激活的应用程序中活动的完全限定类名。

inviteContactActionLabel

在“添加连接”菜单中为inviteContactActivity中指定的活动显示的文本字符串。例如,您可以使用字符串“Follow in my network”。您可以为此标签使用字符串资源标识符。

viewContactNotifyService

应用程序中服务的完全限定类名,应在用户查看联系人时接收通知。此通知由设备的联系人应用程序发送;它允许您的应用程序推迟数据密集型操作,直到需要它们为止。例如,您的应用程序可以通过读入并显示联系人的高分辨率照片和最新的社交流项目来响应此通知。社交流交互部分中更详细地描述了此功能。您可以在SampleSyncAdapter示例应用程序的NotifierService.java文件中看到通知服务的示例。

viewGroupActivity

应用程序中可显示组信息的活动的完全限定类名。当用户单击设备的联系人应用程序中的组标签时,将显示此活动的UI。

viewGroupActionLabel

联系人应用程序为UI控件显示的标签,允许用户查看应用程序中的组。

例如,如果您在自己的设备上安装Google+应用程序并将Google+与联系人应用程序同步,则会在联系人应用程序的“组”标签中看到Google+圈子列为群组。如果您点击Google+圈子,您会看到该圈子中的人员被列为“群组”。在屏幕顶部,您会看到Google+图标;如果您点击它,控制权切换到Google+应用。联系人应用程序使用Google+图标作为viewGroupActionLabel的值,使用viewGroupActivity执行此操作。

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

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。

其他联系人提供商功能

除了前面部分中描述的主要功能外,Contacts Provider还提供了这些用于处理联系人数据的有用功能:

  • 联系小组
  • 照片功能

联系小组

联系人提供者可以选择使用组数据标记相关联系人的集合。如果与用户帐户关联的服务器要维护组,则帐户的帐户类型的同步适配器应在联系人提供程序和服务器之间传输组数据。当用户将新联系人添加到服务器,然后将此联系人放入新组时,同步适配器必须将新组添加到ContactsContract.Groups表。原始联系人所属的组或组使用ContactsContract.CommonDataKinds.GroupMembership MIME类型存储在ContactsContract.Data表中。

如果您正在设计将原始联系人数据从服务器添加到联系人提供程序的同步适配器,并且您没有使用组,则需要告知提供程序使您的数据可见。在用户向设备添加帐户时执行的代码中,更新Contacts Provider为该帐户添加的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.Photo和ContactsContract.RawContacts.DisplayPhoto的参考文档包含检索照片信息的示例。检索原始联系人的主缩略图没有便利类,但您可以向ContactsContract.Data表发送查询,选择原始联系人的_ID,Photo.CONTENT_ITEM_TYPE和IS_PRIMARY列以查找原始联系人的主要联系人照片排。

人的社交流数据还可以包括照片。它们存储在android.provider.ContactsContract.StreamItemPhotos表中,在社交流照片一节中有更详细的描述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值