Contacts Provider

Contacts Provider是一个强大且灵活的组件,它管理着设备上的联系人数据的中央仓库。Contacts Provider是在设备上查看联系人app的数据来源,我们也可以在自己的app中去访问,还可以在设备上在线服务之间传递数据。provider存储了多种类型的数据,对每一个人都尽量去管理多的数据,所以造成了组织结构复杂。因些,provider的API包含了丰富的协定类和接口来方便数据读取和修改。

本文主要讨论以下部分:

  1. provider的基本结构
  2. 如何从prvoider中获取数据。
  3. 如何修改provider中的数据。
  4. 如何构建一个sync adapter来同步我们自己的服务和Contacts Provider的数据。

本文认为已经了解了content provider的基本知识。参阅Content Provider Basics guide。Sample Sync Adapter是一个示例,使用sync adapter来在Contacts Provider和一个基于Google Web Service的示例app之间同步数据。

Contacts Provider的结构

Contacts Provider是一个安卓的系统组件。它维护了人的三种类型的数据,每一种都对应了provider的一张表。如下图所示:


Contacts Provider_第1张图片

图1,Contacts Provider 的表结构

三张表主要通过协定类的名字来命名的。这些协定类定义了content URI,列名,和列的值的常量:
1. ContactsContract.Contacts table
行代表了不同的人,基于原始的人行。
2. ContactsContract.RawContacts table
包含了一个人的所有数据的行。针对特定的帐户和类型。
3. ContactsContract.Data table
包含原始联系人的行,比如邮箱或者电话。

在ContactsContract中其它的表通常是辅助表,Contacts Provider用来管理其它操作或者支持设备上联系人或者电话功能的。

原始联系人

一个原始的联系人表示从一个帐户类型和名称中的一个人的数据。因为Contacts Provider允许多个在线服务作为同一个人的数据源,所以Contacts Provider对同一个人有多个原始联系人数据。多个原始联系人数据可以对同一种帐户类型的多个帐户数据进行组合。

很多原始联系人数据并不是存储在ContactsContract.RawContacts的表中,而是存储在 ContactsContract.Data中。第一行有一个Data.RAW_CONTACT_ID的列,它包含了ContactsContract.RawContacts的行的RawContacts._ID 值。

重要原始联系人的列

重要的原始联系人的列在下表中列举出来了:

表1,重要的原始联系人列


Contacts Provider_第2张图片

备注:
下边的几点对ContactsContract.RawContacts表非常重要:

  1. 原始联系人的名字并不是存储在ContactsContract.RawContacts中,它是存储在ContactsContract.Data中的,ContactsContract.CommonDataKinds.StructuredName列。一个原始联系人在ContactsContract.Data表中只有一行这样的数据。
  2. 注意:为了在原始人行中使用自己的帐户数据,必须首先在AccountManager中进行注册。因为,提醒用户添加帐户类型和帐户名称。如果我们不这么做,Contacts Provider会自动的删除我们的原始联系人行。

比如,如果我们想要app来保存联系人数据到我们的网站(基于域名为com.example.dataservice)的服务,用户的帐户是[email protected],用户必须首先添加帐户的类型(com.example.dataservice)和帐户的名称([email protected]),然后我们才能添加联系人的行。我们可以在文档中向用户说明,或者提醒用户去添加,或者两者同时进行。下面会更详细的解释帐户类型和名称。

原始联系人数据的来源

为了理解 raw contacts 如何工作,考虑用户"Emily Dickinson"在设备上有三个用户帐户:
[email protected]
[email protected]
3.Twitter account "belle_of_amherst"

用户为这三个帐户都开启了Sync Contacts.

假设Emily Dickinson打开了一个浏览器窗口,使用[email protected]登录到Gmail,打开联系人,添加"Thomas Higginson"。然后使用[email protected]登录到Gmail,发送一个邮件给"Thomas Higginson",这样会自动把"Thomas Higginson"添加为联系人。她还在Twitter上关注了"colonel_tom" (Thomas Higginson's Twitter ID).

Contacts Provider会创建三个原始联系人:
1. 一个原始联系人"Thomas Higginson",关联[email protected]。帐户的类型是Google.
2. 第二个"Thomas Higginson"关联[email protected],帐户类型还是Google.尽管名字是一样的,因为是不同的帐户添加的,所以有两个原始联系人。
3. 第三个"Thomas Higginson"关联"belle_of_amherst"。帐户类型是Twitter。

Data

前边已经提到过,原始联系人的数据是存储在ContactsContract.Data中,关联着raw contact的_ID. 这样就允许一个raw contact 可以有多个同一种类型的实例,比如邮箱或者电话。比如,如果[email protected]的"Thomas Higginson"(关联着Google account [email protected]的Thomas Higginson)有个家用邮箱[email protected]和一个工作用的邮箱[email protected], Contacts Provider 存储了两个和raw contact关联的邮箱的行。

注意到不同类型的数据类型存储在一张表中。名字,电话,邮箱,邮编,相片,网站都在ContactsContract.Data表中。为了便于管理,ContactsContract.Data有一些描述性名字的列,其它列使用通用的名字。使用描述性的列不论行中的数据类型是什么,总是有同样的意义,而通用型名称的列会根据数据类型而有不同的意义。

描述性列名

描述性列名的示例:

RAW_CONTACT_ID
数据raw contact的id值

MIMETYPE
这一行数据的数据类型。Contacks Provider使用在ContactsContract.CommonDataKind的子类中定义的MIME types。这些MIME types是公共的开放资源,其它的app或者sync adpater和Contacts Provider合作时都可以使用。

IS_PRIMARY
如果一个raw contact有多个同样的数据类型行,那么IS_PRIMARY会标记出包含这种数据类型主要的数据行。比如,一个用户长按电话号码,然后设为默认,那么 ContactsContract.Data中包含这个号码的行的IS_PRIMARY这一列就会设为非0.

通用列名名称

有15个通用列,名称从DATA1到DATA15,可以普遍使用。还有四个额外的从SYNC1到SYNC4只适用于sync adapters. 通用列名的常量始终起作用,不论数据类型是什么。

DATA1列为索引列。Contacts Provider总是使用这一列在存储那些provider认为最常被查询的数据。比如,在email的行中,这一列是真正的邮箱地址。

通常,DATA15是保留下来存储BLOB的,比如图片。

类型专用的列名
为了能方便处理特定类型的行的列,Contacts Provider也提供了类型专用的列名常量,在ContactsContract.CommonDataKinds中定义。这些常量给同一列不同的常量名来方便我们访问一些特殊类型的行。

比如,ContactsContract.CommonDataKinds.Email类为有Email.CONTENT_ITEM_TYPE这种类型的行数据定义了类型专用的列名常量。这个类包含了常量ADDRESS表示邮箱。它的真实值其实就是data1,也就是列的通用名。

注意:不要使用provider预定义的类型添加自定义的数据到ContactsContract.Data表中。如果这么做了,可能会造成数据丢失或者引起provider的异常。比如,不应该在添加类型是Email.CONTENT_ITEM_TYPE的数据时在DATA1这一列使用user name,正确的做法应该是使用邮箱地址。如果使用了自定义类型,那么就可以自由定义类型的专有列名,按照自己的想法来使用它们。

图2展示了描述性列和数据列如何在 ContactsContract.Data中显示。类型专用列名如何重写了通用列名。


Contacts Provider_第3张图片

类型专用列名类

表2列举了最常见的类型专用列名类:


Contacts Provider_第4张图片

联系人
Contacts Provider把所有的raw contact行通过帐户类型和帐户名称合并来形成一个联系人。这能方便展示和修改用户对某一个联系人收集到的所有数据。 Contacts Provider 管理着新行的创建,raw contact和已有行的合并。系统不允许app或者sync adapter去添加联系人,一些联系人的列只是可读的。

备注:如果使用insert方法插入一个联系人到 Contacts Provider,会抛出UnsupportedOperationException。如果更新一个只读的列,这次更新会被忽略。

Contacts Provider会在添加一个raw contact无法匹配到已经有联系人时创建一个新的联系人。provider也会在更新已有的raw contact造成它无法匹配到它前边关联的联系人时进行创建操作。如果app或者sync adpater创建一个新的联系人可以匹配到已有联系人,那么新的联系人会合并到已有联系人。

Contacts Provider通过Contactsg表中的contact row's _ID来关联contact row和raw contact row. 联系人表ContactsContract.RawContacts的CONTACT_ID列包含了contacts row关联每一行raw contacts的_ID 值。

ContactsContract.Contacts也有一个LOOKUP_KEY的列,它是一个指向contact row的永久性链接。因为 Contacts Provider自动管理联系人,所以它可能会为了合并或者同步而改变一个联系人的row _ID. 即使发生改变时,包含了联系人LOOKUP_KEY的CONTENT_LOOKUP_URI还是会指向联系人的行,所以我们可以使用LOOKUP_KEY来保持指向最喜欢的联系人。这一列有自己的格式,和_id列的格式无关。

图3表明这三个主要的表是如何关联起来的。


Contacts Provider_第5张图片

来自sync Adapter的数据

虽然用户直接把数据输入设备,但是数据也可以通过sync adapter从网络服务中同步到Contacts Provider,这可以在设备和服务间进行数据同步。sync adatper由系统控制在后台运行,他们通过调用ContentResolver来管理数据。

在安卓中,和sync adapter合作的网络服务是通过帐户类型来区分的。每个sync adapter和一个帐户类型协作,但是它可以支持这个类型的多个帐户。在Sources of raw contacts data中我们已经讨论过帐户类型和帐户名称。下边会详细介绍帐户类型和名称是如何关联到sync adapter和service的。

Account type
标记一个用户存储数据的service. 通常,用户必须向服务进行验证。比如,Google Contacts是一个帐户类型,标记为google.com. 这个值对应了AccountManager使用的一个帐户类型。
Account name
标记一个特殊的帐户或者登录一个帐户类型。Google Contacts帐户和Google帐户相同,以一个邮箱地址作为帐户名。其它的服务可能使用一个单词的用户名或者数字id.

帐户类型不必是唯一的。用户可以配置多个Google Contacts accounts,然后下载数据到Contacts Provider.当用户为个人帐户和工作帐户分别配置了一组联系人时,就会发生这种情况。帐户名称通常是唯一的。名称和类型一起可以标记一个特定的Contacts Provider和外部设备间的数据流。

如果想要把service的数据传输到Contacts Provider,需要编写自己的sync adapter.参阅Contacts Provider Sync Adapters.

图4显示Contacts Provider如何融入数据流。在名为sync adapters的方框中,每个adapter都是以帐户类型命名的。

Contacts Provider_第6张图片

需要的权限

想要访问Contacts Provider的app必须申请以下权限:

对一张或多张表的读取权限
READ_CONTACTS,在清单文件中指定,使用节点添加name属性

对一张或多张表的写入权限
WRITE_CONTACTS,在清单文件中指定,使用节点添加name属性

这些权限不适用于用户的个人数据。用户的个人数据和所需的权限会在下边讨论, User Profile。

请记住,用户的联系人数据是私有并且敏感的。用户非常注重隐私,所以他们不想让app收集他们的信息或者他们的人联系人信息。 如果我们申请权限的理由不充分,他们可能给我们的app差评或者不安装。

用户个人信息

ContactsContract.Contacts表中有一行专门用来存储设备用户的个人信息。这个数据描述了设备的用户而不是他的联系人。每个使用了个人资料的系统都会有一行raw contact指向这个个人资料的行。每个个人资料行会有多很行的数据。访问个人信息的常量可以在ContactsContract.Profile类中找到。

访问用户的个人信息需要特殊的权限。除了READ_CONTACTS和WRITE_CONTACTS之外,还需要在清单文件中声明READ_PROFILE和WRITE_PROFILE.

请记住用户的个人信息是敏感的。READ_PROFILE可以让我们访问设备上的用户个人信息。一定要告诉用户我们的app为什么需要这个权限。

为了获取包含了用户个人信息的数据,调用ContentResolver.query()。设置CONTENT_URI,不要提供任何选择条件。也可以使用这个content URI作为查询raw contact或者个人资料的基本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 Provider的元数据

Contacts Provider管理用于追踪仓库中联系人状态的数据。这种关于仓库的元数据在多个地方存储,包括Raw Contacts,Data和Contacts表,ContactsContract.Settings表,ContactsContract.SyncState表。下边的表展示了这些元数据的作用:
表3, Contacts Provider的元数据


Contacts Provider_第7张图片

Contacts Provider_第8张图片

Contacts Provider的访问

这部分描述访问Contacts Provider,主要关注以下部分:

1.实体查询
2.批量修改
3.使用intent来获取和修改数据
4.数据完整性

使用sync adapter来修改数据,请参阅Contacts Provider Sync Adapters

实体查询

因为Contacts Provider是以层级结构来管理的,所以获取一行和它的所有关联的子行就非常方便。比如,为了显示一个人的所有信息,我们可能需要获取某个 ContactsContract.Contacts行所有关联的ContactsContract.RawContacts的行,或者一个ContactsContract.RawContacts的所有 ContactsContract.CommonDataKinds.Email行。为了方便这种操作,Contacts Provider提供了实体构造,就像数据库中的表关联.

一个实体类似一张表,由父表和子表的选定列组成。当我们查询一个实体时,我们提供查询参数和查询条件。结果是一个Cursor,每个子表的行都在Cursor中有对应的行。比如,我们查询ContactsContract.Contacts.Entity的一个name和 ContactsContract.CommonDataKinds.Email中的所有那个name的行,我们会得到一个Cursor,每一行的ContactsContract.CommonDataKinds.Email都在Cursor中都有对应的行。

实体简化的查询。使用实体,我们可以一次性获取一个contact或者raw contact的所有联系人数据,而不再是先去查询父表得到一个id,然后再使用id去询子表。同样的,Contacts Provider在一个事务中处理查询,保证了获取数据是一致的。

备注:一个实体通常不包含父表和子表所有的列,如果我们使用的列名没有在实体中,那么就会抛出异常。

下边的代码说明了如何查询一个联系人的所有raw contact. app有两个activity, "main"和"detail"。main activity展示了一个contact的列表,当用户选择一个时,activity把id发送到detail activity. detail activity使用这个ContactsContract.Contacts.Entity来展示所有关联的raw contacts。

detail 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 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。在我们的app中,我们会从这个Cursor中得到数据并展示和供未来的使用。

批量修改

在对Contacts Provider进行insert, update, 和 delete 操作时,要尽可能的使用批量模式,创建一个ContentProviderOperation的ArrayList,然后调用applyBatch。因为Contacts Provider是在一个事务中通过applyBatch来完成所有的操作,所以我们的修改永远不会把联系人数据变得不一致。批量修改也有利于对一个联系人和他的详细信息同时插入。

备注:为了修改一个联系人,发送intent给联系人app而不是自己处理可能更好。参阅Retrieval and modification with intents

Yield points
一个包含了大量操作的批量修改可能会阻塞其它线程,导致不好的用户体验。为了让所有的操作在尽量少的表中,同时防止他们阻塞系统,我们可以设置yield points. yield point是一个isYieldAllowed为true的ContentProviderOperation对象。当Contacts Provider碰到一个yield point时,它会暂停当前的工作,让其它进程运行前终止当前的事务。当provider重新启动时,会重新开启一个事务运行ArrayList中的下一个操作。

Yield point可以让每次调用applyBatch有多个事务。因此,我们可以设置为一组相关数据的最后一个操作设置。比如,可以为一组添加raw contact和它相关的数据的最后一个操作设置一个yield point,或者为一组和一个联系人相关的数据设置一个.

Yield point也可以是一个原子操作。现个Yield point之间的访问不管是成功还是失败都是以一个单元的形式出现的。如果不设置任何Yield point,最小的操作单元就是整个批处理操作。如果使用了Yield point,我们就可以防止操作来降低用户体验,同时保证操作的原子性。

修改向后的引用
当我们插入一个新的raw contact行和它关联的数据时,我们会把它作为一组ContentProviderOperation,这时,必须把它们关联起来,插入的raw contact's _ID就是RAW_CONTACT_ID的值。但是这个值在创建ContentProviderOperation时并没有。为了解决这个问题,ContentProviderOperation.Builder类有一个方法withValueBackReference().这个方法可以让我们使用前一个操作的结果来插入或者修改一列。

withValueBackReference()有两个参数:

1.key
一个键值对的key.这个参数的值是我们要修改表的列的列名。

2.previousResult

applyBatch的ContentProviderResult数组中以0开头的值。batch operation执行后,每个操作的结果都会存储在一个中间的结果数组中。previousResult的值是这些结果的一个index,它可能通过key值来获取和索引。这就可以上我们插入一个新的记录,得到它的_id,然后在我们添加ContactsContract.Data时使用一个向后引用。

第一次调用applyBatch时,整个结果数组就会被创建,数组的大小和ContentProviderOperation的ArrayList大小相同。但是,当操作没有执行时,如果我们使用向后引用,结果集中的元素全为null,withValueBackReference会抛出异常。

下边的代码展示了如何批量插入一个raw contact和数据。代码中有创建yield point和使用一个向后引用。是Contact Manager示例app中ContactAdder类的createContacEntry方法。

第一段代码从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 ops =
            new ArrayList();

    /*
     * 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());

下边,代码创建了display name, phone,和 email等行数据。

每个操作的builder都使用withValueBackReference来得到一个RAW_CONTACT_ID。这个引用指向后边的ContentProviderResult的结果,这个结果是第一次操作添加联系人后返回的一个新的_ID.结果是,每一行数据都会通过RAW_CONTACT_ID自动关联到对应的ContactsContract.RawContacts。

添加邮箱的ContentProviderOperation.Builder对象有一个 withYieldAllowed的标记,用于设置yield point:

    // 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来插入新的raw contact和数据。

    // 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.在获取其它数据时,也要获取raw contact的VERSION列。
2.创建一个适合使用 newAssertQuery(Uri)来添加一个约束的ContentProviderOperation.Builder。对于content URI,在RawContacts.CONTENT_URI后边追加 raw contact's _ID。
3.对于ContentProviderOperation.Builder,调用withValue来比较刚刚获取到的VERSION列。
4.对于相同的ContentProviderOperation.Builder,调用withExpectedCount来保证只有一行通过断言来测试。
5.调用build()方法来创建ContentProviderOperation,然后添加到我们给传递给applyBatch的ArrayList的第一个位置。
6.执行这个批处理事务。

如果raw contact在我们读取和要修改之间被其它操作修改了,这个断言会失败,整个批处理会终止。我们可以选择重新来过或者做其它操作。

下边的代码演示了使用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 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;

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到contacts app可以让我们间接访问Contacts Provider。intent会启动contacts app的UI,用户可以做相关的操作。通过这种访问,用户可以:

1.从列表中选择一个联系人,把它返回到我们的app做后续工作。
2.编辑一个已经的联系人数据。
3.为任何帐户插入一个新的raw contact.
4.删除一个联系人或者联系人数据。

如果用户需要插入或者更新数据,我们可以先收集数据后,再把数据作为intent的一部分,一起发送。

当我们使用intent通过contacts app来访问Contacts Provider时,我们不需要写自己的ui和访问provider的代码。也不需要申请读写权限。contacts app会把一个联系人的读取权限委托给我们,因为我们是通过另外一个app来做修改的,所以也不需要写的权限。

发送intent来访问provider的通常流程已经在Content Provider Basics的Data access via intents中讨论过了。我们需要使用到action, MIME type和数据在下边的表中总结了,我们可以通过putExtra来添加的extras在ContactsContract.Intents.Insert中:


Contacts Provider_第9张图片

Contacts Provider_第10张图片

contacts app不允许用户通过intent删除数据。为了删除一个raw contact,请使用 ContentResolver.delete()或者 ContentProviderOperation.newDelete()。

下边的代码说明了如何构建和发送一个插入新raw contact和数据的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 contactData = new ArrayList();


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

数据完整性

因为contacts仓库包含的数据都非常重要和敏感,所以用户希望它们是正确的并且是实时的。Contacts Provide为了数据的完整性有一些清晰规划的准则。当我们在修改数据时,需要遵守这些准则。下边是重要的准则:

1. 为每个要添加的ContactsContract.RawContacts row添加一行ContactsContract.CommonDataKinds.StructuredName。
  一个没有ContactsContract.CommonDataKinds.StructuredName行的ContactsContract.RawContacts可能会在合并的时候出错。
2. 总是把新的ontactsContract.Data关联到他们的父ContactsContract.RawContacts行。
一个没有关联 ContactsContract.RawContacts的ContactsContract.Data是不能被contacts app识别的,可能会导致sync adapter出错。
3. 只改变我们有的那些raw contacts
切记,Contacts Provider管理了不同帐户类型和在线服务的数据。我们需要保证app只修改或删除了我们自己的数据,添加数据时要通过我们控制的账户类型和帐户名称。
4. 对于authorities, content URIs, URI paths, column names, MIME types, 和TYPE values,总是使用ContactsContract和子类中的常量。
使用这些常量能避免出错。当有常量过时时,编译器也会通知我们。

自定义数据行

通过创建和使用我们自定义的MIME types,我们可以在ContactsContract.Data表中插入,编辑,删除,获取我们自己的数据。虽然我们可以把自己的类型列映射到默认的列名,这些行也只能使用我们在 ContactsContract.DataColumns中定义的列。在contacts app中,我们的数据可以展示,但是不能编辑或删除,用户也不能添加额外的数据。为了允许用户来修改我们的数据,必须在自己的app中提供一个编辑的activity.

为了展示我们的数据,提供一个 contacts.xml文件,包含一个节点和一个或多个子节点。参阅

参阅 Creating a Content Provider

Contacts Provider Sync Adapters

Contacts Provider是专门设计来处理联系人数据在设备和在线服务间的同步。这可以让用户下载已经有的数据到一个新设备上和上传已有的数据到一个新帐户。同步也能保证用户的数据是实时的,不需要考虑添加或者变更。另外一个优点是它可以让设备在离线时还可以使用联系人数据。

虽然我们可以多种方式实现同步,系统内置的同步框架可自动化完成以下任务:
1.检查网络。
2.根据用户偏好来安排和执行同步。
3.重启停止的同步。

为了能使用这个框架,我们提供一个sync adapter,每个sync adapter对于一个service和content provider都是唯一的,但是可以处理一个服务多个账户。框架可以支持同一个servcie和provider的多个sync adapter。

Sync adapter classes and files

我们自己实现一个AbstractThreadedSyncAdapter,然后作为app的一部分进行安装。系统会从清单文件中的节点以及指向一个特定XML文件来了解到这个sync adapter。XML文件定义了帐户类型和content provider的权限,它们可以唯一标记一个adapter. sync adapter直到用户为adapter的帐户类型添加一个帐户并且启动了content provider的同步后才会开始同步。这时,系统开始管理adapter,必要时在content provider和server之间进行数据同步。

备注:使用帐户类型作为sync adapter的标记可以允许系统来识别和分组同一个组织访问不同服务的adapter。比如,google online service的sync adapter都有相同的帐户类型com.google.当用户添加一个帐户类型时,所有安装的sync adapter都会列出来。每一个sync adapter都会在设备上同步一个不同的content provider.

很多service访问数据前需要用户来确定他们的身份,系统提供了身份验证框架,这个框架和sync adapter框架类似,并且经常与其联用。身份验证框架使用内置的AbstractAccountAuthenticator的子类验证器。验证器通过下边的几步来验证用户的身份:

  1. 收集用户的名字,密码或其它信息(用户的凭据)。
  2. 发送这些凭据到服务端。
  3. 检查服务器的回复。

如果服务器接收了凭据,验证器就会把凭据存下来供后续使用。因为内置的验证器框架,AccountManager可以提供验证器支持并公开的任何token(比如OAuth2)来访问。

虽然验证不是必须的,但是大多contacts service还是会使用它。但是我们不一定要使用安卓的验证框架来做验证。

Sync adapter implementation

为了实现一个Contacts Provider的sync adapter,我们可以创建包含以下部分的app:

  1. 一个Service组件,可以响应系统发出绑定到这个sync adapter的请求。
    当系统想要同步时,会调用service的onBind来得到一个IBinder对象。这可以让系统做跨进程间调用adapter的方法。
    在Sync Adapter的示例中,service的name是com.example.android.samplesync.syncadapter.SyncService.
    2.实现了 AbstractThreadedSyncAdapter的一个真正的sync adapter.
    这个类完成了下载,上传和解决冲突的工作。主要工作是在onPerformSync方法中完成的。这个类必须是单例的。
    在Sync Adapter的示例中,sync adapter是在com.example.android.samplesync.syncadapter.SyncAdapter中定义的。
    3.实现application
    这个类是sync adapter单例的工厂类。在onCreate中初始化这个sync adapter,提供静态的get方法,返回一个实例.
    4.可选:一个servcie组件,来响应系统发起的用户认证请求。
    AccountManager启动这个service来开启验证进程。这个service的onCreate中实例化一个验证器。当系统想要为app的sync adapter来验证用户帐户时,会调用service的onBind得到一个IBinder。这可以让系统做跨进程间调用验证器的方法。
    在Sync Adapter的示例中,service的name是com.example.android.samplesync.authenticator.AuthenticationService.
    5.可选:实现AbstractAccountAuthenticator来处理验证请求。
    这个类提供方法来让AccountManager来调用验证用户的凭据。具体的验证根据服务端的技术差别很大。请参阅服务端的技术文档。
    在Sync Adapter的示例中,验证器是在com.example.android.samplesync.authenticator.Authenticator中定义的.

定义sync adapter和验证器的XML文件
前边讨论的sync adapter和验证器在app的清单文件中节点定义。这些节点包含了子节点来提供指定的数据:
1.sync adapter service的指向了res/xml/syncadapter.xml。然后,这个文件定义了Contacts Provider要同步的web serivice的URI和帐户类型。
2.验证器的指向了res/xml/authenticator.xml。然后,这个文件指定了验证器支持的帐户类型,和在验证时的UI. 指定的帐户类型必须和sync adapter指定的帐户类型相同。

社交流数据

android.provider.ContactsContract.StreamItems和android.provider.ContactsContract.StreamItemPhotost管理了从社交网络传入的数据。我们可以构建一个sync adapter来从自己的网络上数据添加到这些表中,也可以从这些表中读取数据展现到我们的app中,或者两个都做。通过这些方式,我们的社交网络服务和app可以集成到Android的社交网络体验中。

社交流文本
Stream items总是和raw contact关联。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID关联着raw contact的_ID。 raw contact的account type和name都存储在stream item row中。

把stream中的数据存储在以下的列:
1. android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
必须的,用户的account type关联着这个stream item. 记得在插入时设置这个值。
2. android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必须的,用户的account name关联着这个stream item. 记得在插入时设置这个值。

  1. Identifier columns
    必须的,在插入时一定要插入以下identifier列:
    a. android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: 关联android.provider.BaseColumns#_ID
    b. android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: 关联android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY
    c. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: 关联android.provider.BaseColumns#_ID
  2. android.provider.ContactsContract.StreamItemsColumns#COMMENTS
    可选的,存储那些可以在开头显示的概要信息。
    5.android.provider.ContactsContract.StreamItemsColumns#TEXT
    文本信息,或者item源发布的内容,或者是对生成stream item的操作描述。这个列可以包含可以被fromHtml()渲染的任何形式和包含任何图片内容。provider可能会截断或压缩长的内容,但它会尽量避免破坏tags.
    6.android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
    包含了stream item插入和更新时间的文本。app在插入和更新时需要维护这一列,provider不会自动维护。

为了展示stream item的标记信息,使用android.provider.ContactsContract.StreamItemsColumns#RES_ICON,android.provider.ContactsContract.StreamItemsColumns#RES_LABEL和android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE链接我们的app资源。

android.provider.ContactsContract.StreamItems表也包含了android.provider.ContactsContract.StreamItemsColumns#SYNC1到android.provider.ContactsContract.StreamItemsColumns#SYNC4的列来支持sync adapter.

Social stream photos
android.provider.ContactsContract.StreamItemPhotos表存储了关联stream item的图片。表的android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID关联了android.provider.ContactsContract.StreamItems的id.图片的引用存储在表中的这些列:
1.android.provider.ContactsContract.StreamItemPhotos#PHOTO column (a BLOB).
图片的二进制表现,为了存储和展示,provider会重新调整大小。这个列可以向前兼容存储之前版本的provider的图片,但是,在新版本中,我们不应该使用这个列,而应该使用 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID 或者 android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(下边会介绍)来存储图片到文件。这一列包含了图片的方便读取的缩略图。
2.android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
raw contact的图片的数字标识。追加这个值到DisplayPhoto.CONTENT_URI来得到一个指向一个图片文件的content URI,然后调用 openAssetFileDescriptor()来得到文件的句柄。
3.android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
一个直接指向图片文件的content URI. 调用openAssetFileDescriptor()会得到文件的句柄。

使用社交数据流的表

这些表和contacts provider中的其它表基本相同,除了以下这几点:

1.这些表需要额外的访问权限。为了能够读取,app必须有android.Manifest.permission#READ_SOCIAL_STREAM,为了修改,app必须有 android.Manifest.permission#WRITE_SOCIAL_STREAM.
2.对于 android.provider.ContactsContract.StreamItems表来说,每个raw contact可以存储的行数是有限的。一旦达到限制,Contacts Provider会自动删除 android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP最早的行来提供空间。要查询这个限制,查询android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. 可以将content URI以外的所有设为null. 查询返回一个一行的Cursor,包含了android.provider.ContactsContract.StreamItems#MAX_ITEMS

android.provider.ContactsContract.StreamItems.StreamItemPhotos定义了android.provider.ContactsContract.StreamItemPhotos的子表,包含了stream item的图片行。

Social stream interactions

Contacts Provider管理的社交流数据和contacts app结合可以在社交网络和已经有联系人之间建立有效的连接。下边的几点很重要:

  1. 通过把社交网络服务和 Contacts Provider同步,我们可以获取用户联系人的最近活动安排,然后把它存储到android.provider.ContactsContract.StreamItems 和 android.provider.ContactsContract.StreamItemPhotos,方便后续使用。
  2. 除了定期的同步,我们也可以在用户选择一个联系人查看时让sync adapter主动获取额外的数据。这就可以让sync adapter来获取高清的图片和最近的社交数据。
  3. 通过在contact app和 Contacts Provider注册一个通知,当一个联系人被查看时,我们可以接收到一个intent,这时,从我们的服务中更新contact的状态。和做全量的同步相比,这样更快也节省资源。
  4. 用户在contacts app上查看一个contact时,可以添加一个contact到我们的社交服务。我们可以通过"invite contact"来开启这个功能,它可以把一个activity和一个XML文件结合起来。activity添加一个已有联系人到我们的网络,XML文件把我们app的细节通知给contacts app和 Contacts Provider 。

stream items和Contacts Provider的定期同步和其它同步相同,参阅Contacts Provider Sync Adapters。注册一个通知和邀请联系人在下边讨论。

Registering to handle social networking views

注册我们的sync adapter当用户查看我们的sync adapter管理的联系人时接收通知,需要以下几步:

  1. 在res/xml/下创建一个contacts.xml,如果已经有这个文件,可以跳过。
  2. 在文件中添加 节点。如果节点已经存在,跳过.
  3. 注册一个服务,当用户在contacts app中打开一个联系人详情页时接收通知,添加 viewContactNotifyService="serviceclass"属性到节点,serviceclass是service的类名。对于这个service, 为了让service可以接收Intent,请使用IntentService. intent中的数据包含了用户点击的raw contact的content URI. 我们可以绑定这个服务然后调用sync adapter来更新这个raw contact的数据。

注册一个activity当用户点击一个 stream item或者photo或者两者一起时调用:

  1. 在res/xml/下创建一个contacts.xml,如果已经有这个文件,可以跳过。
  2. 文件中添加 节点。如果节点已经存在,跳过.
  3. 注册一个activity来处理用户在contacts app中点击stream item,添加viewStreamItemActivity="activityclass" 属性到节点,activityclass是activity的全类名。这个activity应该接收设备contacts app的intent.
  4. 注册一个activity来处理用户在contacts app中点击一张stream photo的事件。添加viewStreamItemPhotoActivity="activityclass"属性到节点,activityclass是activity的全类名。这个activity应该接收设备contacts app的intent.

参阅

intent包含了用户点击的item 或者photo的content URI. 使用不同的属性,来让不同的activity来分别处理文本和图片。

Interacting with your social networking service

用户不需要离开contact app就可以邀请一个contact到我们的社交网络。我们可以让app发送一个intent来通知我们的activity. 要设置这个功能,注意以下几步:

  1. 在res/xml/下创建一个contacts.xml,如果已经有这个文件,可以跳过。
  2. 文件中添加 节点。如果节点已经存在,跳过.
  3. 添加以下属性inviteContactActivity="activityclass"
    inviteContactActionLabel="@string/invite_action_label" activityclass是activity的全类名。这个activity应该接收设备contacts app的intent. invite_action_label是一个文本,用来展示在contacts app的添加联系人菜单上。

注意:ContactsSource是过期的tag name.

contacts.xml reference

contacts.xml文件中包含了XML节点,控制了我们的sync adaptert及app和contacts app,Contacts Provider之间的交互。这些节点在下边部分介绍。

element
控制了app和contacts app之间的交互,有以下的语法:


包含它的文件:
res/xml/contacts.xml

可以包含:

描述

声明组件和UI标签,让用户可以邀请一个contact到社交网络,通知用户他们的streams更新了或者其它。

注意属性的前缀android对来说不是必须的。

属性:

inviteContactActivity
app中的activity的全称,当用户从contact app中选择添加一个联系人时激活这个activity.

inviteContactActionLabel
在Add connection菜单上显示的inviteContactActivity的文本形式。比如,我们可以使用 "Follow in my network",也可以使用引用字符串。

viewContactNotifyService
app中的service全称,当用户查看一个联系人时这个service会接收到一个通知。这个通知是由contact app发出的,它可以让app来推迟那些数据频繁的操作到需要的时候再执行。比如,app可以在接收到这个通知后,展示联系人的高清图片和常用的social stream items. 请参阅Social stream interactions. 我们可以在SampleSyncAdapter示例中的NotifierService.java文件查看示例。

viewGroupActivity
app展示群信息的activity全称。 当用户点击了contacts app的一个组时,这个activity会展示出来。

viewGroupActionLabel
contacts app给用户在我们app中查看组的一个入口标签。

比如,如果我们安装了google+ app,然后和contacts app进行同步。 我们可以google+会在contact app中作为一个tab出现。如果我们点击了google+, 我们可以看到这些联系人会作为一个组。 在开头,我们会看到google+的icon,如果我们点击了,会切换到google+ app。 contacts app通过 viewGroupActivity来实现这个操作,使用Google+ icon作为viewGroupActionLabel.

string resource也可以使用。

viewStreamItemActivity
当用户点击了一个raw contact的stream item时,contacts app会启动我们app的一个activity的全称。

viewStreamItemPhotoActivity
当用户点击了一个raw contact的stream item的图片时,contacts app会启动我们app的一个activity的全称

element

控制了app自定义数据在ocntact app中的显示。语法如下:


包含以下部分:


描述

使用这个节点让contact app展示自定义数据,每个的子节点代表了一种我们的sync adpater添加到 ContactsContract.Data表中的自定义数据的行。添加一个节点到每个自定义的MIME type。如果自定义数据不需要显示,就不需要添加这个节点。

属性:

android:mimeType
我们为在 ContactsContract.Data中自定义数据行定义的MIME type. 比如,vnd.android.cursor.item/vnd.example.locationstatus 可以是记录contact最后地址的数据行的MIME type.

android:icon
contact app展示我们数据旁边添加的一个drawable resource. 使用这个表明数据是从我们的服务中来的。

android:summaryColumn
数据行中的两个值的第一个列名。数据行的入口第一行。第一行可以是摘要,是可选的,请参阅android:detailColumn

android:detailColumn
数据行中的两个值的第二个列名。数据行的入口第二行,请参阅android:summaryColumn

其它contacts provider的功能

除了上边的主要功能外, Contacts Provider还提供了和以下联系人数据有关的实用功能:
1.Contact groups
2.Photo features

Contact groups

Contacts Provider可以选择性的为相关的联系人分组。如果服务端送到的一个用户帐户想要分组,sync adapter应该把组数据在Contacts Provider和server之间进行同步。 当用户添加一个新的联系人到server, 然后添加到一个新的group, sync adapter必须添加这个组到 ContactsContract.Groups表中。raw contact所属的组信息存储在ContactsContract.Data中,使用了ContactsContract.CommonDataKinds.GroupMembership MIME type.

如果我们设计一个sync adapter可以从server添加联系人到Contacts Provider, 没有使用groups. 那么我们需要告诉Contacts Provider来让我们的数据可见。 当用户一个帐户到设备上时,更新Contacts Provider添加这个account的ContactsContract.Settings行。在这一行,设置Settings.UNGROUPED_VISIBLE的列值为1。这样,Contacts Provider会总是让我们的数据可见,即使我们没有使用group.

Contact photos
ContactsContract.Data表存储图片时使用了Photo.CONTENT_ITEM_TYPE作为MIME type。这一行的CONTACT_ID关联着raw contact的_id. ContactsContract.Contacts.Photo定义了ContactsContract.Contacts的一个子表,这个表包含了联系人的主要相片的信息。同样的,ContactsContract.RawContacts.DisplayPhoto定义了一个ContactsContract.RawContacts的一个子表,这个表包含了raw contact的主要相片的信息。

请参阅ContactsContract.Contacts.Photo和ContactsContract.RawContacts.DisplayPhoto. 我们没有简单的方法来获取一个raw contact的主要缩略图,但是我们可以查询ContactsContract.Data表,选择raw contact's _ID, the Photo.CONTENT_ITEM_TYPE, 和the IS_PRIMARY column来查找raw contact的主要图片行。

Social stream data也可能有图片。这些存储在android.provider.ContactsContract.StreamItemPhotos表中,请参阅Social stream photos

你可能感兴趣的:(Contacts Provider)