标签:
今年加入了某字幕组,加之杂事颇多,许久未添新文了,惭愧之极。 在听闻 Google 即将重返中国后,近日忽又发现官方网站正在放出 API 中文版,比如本文。当然不是大家所译,但至少句子结构较通顺,窃以为比 MSDN 中文版好些。虽有些生硬(比如将 Provider 译为“提供者”,有趣得紧),但好在前无古人,也许 Google 自此便统一了自己的中文术语也未可知。能让更多的国人精确领悟 Android 的精髓,肯定是好事,希望 Google 继续坚持。
这事应该喜大普奔啊,怎么没见报道啊?当年加入的翻译组成了余党,现在余党也已消失了,虽开始便知是迟早的事,仍不免生出许多悲凉。本人以后将不再出这些代庖译文了。
英文原文:http://developer.android.com/guide/topics/providers/contacts-provider.html
采集日期:2014-5-18
官方译文:http://developer.android.com/intl/zh-cn/guide/topics/providers/contacts-provider.html
ContactsContract.Contacts
ContactsContract.RawContacts
ContactsContract.Data
ContactsContract.StreamItems
Contacts Provider 是一种强大而灵活的 Android 组件,它管理着当前设备中的联系人信息数据库。 Contacts Provider 是“联系人”应用的数据源,其他应用也可以访问其中的数据,并且可以在当前设备和在线服务之间传输数据。 Contacts Provider 可容纳多种数据源,并且为了尽可能将人员信息全部管理起来,它的组织架构是比较复杂的。 因此,Contacts Provider 的 API 包含了大量的契约(contract)类和接口,为读取和修改数据提供便利。
本文主要介绍了:
本文假定读者已对 Android 的 Content Provider 有了初步的了解。 有关 Content Provider 的更多信息,请参阅开发指南中的 Content Provider 基础。 Sync Adapter 范例 中给出了一个应用实例,利用 Sync Adapter 在 Contacts Provider 和托管于 Google Web Service 内的一个示例应用之间进行数据传输。
Contacts Provider 是一个Android Content Provider 组件。 它维护着三种人员信息相关的数据,每种数据对应着 Provider 中的一个表,如图 1 所示:
这三个表通常通过它们的契约类名称来引用。 这些类为下列数据表的 Content URI、字段名和字段值进行了对应常量的定义:
ContactsContract.Contacts
表
ContactsContract.RawContacts
表
ContactsContract.Data
表
ContactsContract
中还有其他一些辅助表,也是由契约类来代表的,都是 Contacts Provider 用来进行操作管理的,或是用于向“联系人”或“电话”应用提供特定功能的。
一个 Raw Contact 代表由账户类型和账户名唯一确定的人员信息。 因为 Contacts Provider 允许同一联系人使用多个在线服务作为数据源,所以它允许一个人对应多个 Raw Contact。 正因如此,一个用户的数据可由相同账户类型的多个账户归并生成。
Raw Contact 的大部分数据并不是保存在
ContactsContract.RawContacts
表中。而是存放于
ContactsContract.Data
表的一条或多条记录中。每行数据都有一个
Data.RAW_CONTACT_ID
字段,存放着上一级
ContactsContract.RawContacts
的记录
RawContacts._ID
。
表 1 中列出了ContactsContract.RawContacts
中的重要字段。请注意表中的注意事项。
字段名称 | 用途 | 注意事项 |
---|---|---|
ACCOUNT_NAME
|
该 Raw Contact 数据来源的账户名称,与账户类型呼应。
比如,Google 账户的名称是个 Gmail 地址。详情请参阅下面的
ACCOUNT_TYPE 。
|
账户名称的格式根据账户类型而定。不一定是 Email 地址。 |
ACCOUNT_TYPE
|
该 Raw Contact 数据来源的账户类型。比如,Google 账户的类型是 com.google 。
请务必使用拥有所有权或控制权的域名作为账户类型的修饰符,以保证账户类型的唯一性。
|
联系人的账户类型通常与某个 Sync Adapter 关联起来,以便与 Contacts Provider 同步数据。 |
DELETED
|
该 Raw Contact 的“删除”标志。 | 本标志允许 Contacts Provider 在内部暂时保留这条记录,等到 Sync Adapter 完成服务器上的删除操作后,再从数据库中最终删除这条记录。 |
以下是有关
ContactsContract.RawContacts
表的重要事项:
ContactsContract.RawContacts
的记录中。而是保存在
ContactsContract.Data
表中,类型为
ContactsContract.CommonDataKinds.StructuredName
。每个 Raw Contact 在
ContactsContract.Data
表中只存在一条该类型的数据。
AccountManager
进行注册。只要让用户把账户类型和账户名称加入账户列表即可。
如果未经注册,Contacts Provider 将会自动删除自行加入的 Raw Contact 数据行。
比如,假设应用程序需要对一个基于 Web 服务的联系人信息进行管理,
服务的域名是 com.example.dataservice
,
服务的账户类型是 becky.sharp@dataservice.example.com
,
那么在添加 Raw Contact 记录之前,
首先必须让用户添加账户“类型”(com.example.dataservice
)和账户“名称”(becky.smart@dataservice.example.com
)。
可以在应用程序的文档中向用户说明这一要求,也可以在程序中提醒用户添加类型和名称。
关于账户类型和账户名称的更多细节,将在下一节中介绍。
为了加深对 Raw Contact 的理解,以下举例说明,假定用户“Emily Dickinson”在设备中拥有以下三个账户:
emily.dickinson@gmail.com
emilyd@gmail.com
该用户在系统设置项 账户 中对这三个账号都启用了 同步联系人功能。
假设 Emily Dickinson 打开浏览器窗口,用 emily.dickinson@gmail.com
登录 Gmail,并打开联系人,添加“Thomas Higginson”。
然后,她又用 emilyd@gmail.com
登录 Gmail 并发送邮件给“Thomas Higginson”,这会自动将其添加为联系人。
同时,她还在 Twitter 上关注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。
完成上述操作后,Contacts Provider 将会生成以下三条 Raw Contact 记录:
emily.dickinson@gmail.com
。
账户类型为 Google。
emilyd@gmail.com
。
账户类型也是 Google。由于对应的用户帐户不同,尽管第二条记录的名称与前一条的完全相同,还是成为了第二个 Raw Contact。
如前所述,Raw Contact 的数据存放在
ContactsContract.Data
中,并通过 Raw Contact 记录的 _ID
关联。 这样每个 Raw Contact 对每种数据(如 Email 地址或电话号码)都可以拥有多个实例。
例如,“Thomas Higginson”的 Raw Contact 记录为 emilyd@gmail.com
(通过 Google 账户 emilyd@gmail.com
关联),
她的家用 Email 地址是 thigg@gmail.com
,工作 Email 地址是 thomas.higginson@gmail.com
,
Contacts Provider 就会存储两条 Email 记录,且均通过 ID 与 Raw Contact 记录关联。
请注意,Data 表中存放着多种类型的数据。显示姓名、电话号码、Email、邮寄地址、照片和网站详情等信息都可以在
ContactsContract.Data
表中找到。为了便于管理,
ContactsContract.Data
表的有些字段名称是描述性的,另一些则是通用名称。
描述性名称的字段与数据的类型无关,内容的含义与字段名称相同。
通用名称字段中的内容,则会根据不同的数据类型而具有不同的含义。
以下是一些描述性字段名称的例子:
RAW_CONTACT_ID
_ID
。
MIMETYPE
ContactsContract.CommonDataKinds
中定义的 MIME 类型。这些 MIME 类型都是公开定义的,可以被应用程序或 Contacts Provider 对应的 Sync Adapter 直接使用。
IS_PRIMARY
IS_PRIMARY
字段就标识了哪一行数据是该类信息的主数据。
比如,如果用户长按某联系人的电话号码,并选择 设为默认号码,那么包含该号码的
ContactsContract.Data
数据行的
IS_PRIMARY
字段就会被置为非零值。
目前可用的通用字段有15个,名字分别为 DATA1
到 DATA15
。
另外还有4个通用字段是 Sync Adapter 专用的,命名为 SYNC1
到 SYNC4
。
通用字段名称随时可用,与数据行的类型无关。
DATA1
字段自带索引。
Contacts Provider 始终会使用这一字段,将其视为查询语句中最常用的数据所在。
比如,在 Email 数据行中,本字段就存放了实际的 Email 地址。
按照惯例,DATA15
是保留字段,用于存放二进制大对象(BLOB)数据,比如缩略图。
为了简化对某些特定类型数据的操作,Contacts Provider 也支持一些固定类型的字段名称,这些常量在
ContactsContract.CommonDataKinds
的子类中定义。通过这些常量,就可以方便地访问同名的数据字段。
例如,
ContactsContract.CommonDataKinds.Email
类中就为 MIME 类型为
Email.CONTENT_ITEM_TYPE
的
ContactsContract.Data
数据行定义了一些字段。该类中包含了 Email 地址字段
ADDRESS
。
ADDRESS
的值实际就是通用字段名称“data1”。
注意:
不要套用 Provider 预定义的 MIME 类型在
ContactsContract.Data
表中添加自定义的数据。这可能会导致数据丢失或 Provider 的功能异常。
比如,请勿在 DATA1
字段中添加 MIME 类型为
Email.CONTENT_ITEM_TYPE
的用户名数据,这里本应是存放 Email 地址的。
如果该行使用的是自定义的 MIME 类型,那就可以定义任意类型的字段名称,并随意使用各个字段。
图 2 演示了描述性字段和 data 字段在
ContactsContract.Data
表中的位置,以及固定类型的字段名“覆盖”通用字段名的情况。
表 2 列出了大部分常用的固定类型字段名称类:
映射类 | 数据类型 | 注意事项 |
---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
本条记录相关 Raw Contact 的姓名。 | 每个联系人只能有一条该记录。 |
ContactsContract.CommonDataKinds.Photo |
本条记录相关 Raw Contact 的主照片。 | 每个联系人只能有一条该记录。 |
ContactsContract.CommonDataKinds.Email |
本条记录相关 Raw Contact 的 Email 地址。 | 每个联系人可以有多个 Email 地址。 |
ContactsContract.CommonDataKinds.StructuredPostal |
本条记录相关 Raw Contact 的邮寄地址。 | 每个联系人可以有多个邮寄地址。 |
ContactsContract.CommonDataKinds.GroupMembership |
联系人在 Contacts Provider 中所属的组。 | 群组是账户类型与名称之外的可选功能。详情请参阅 联系人群组。 |
根据账户类型和账户名称,Contacts Provider 将多条 Raw Contact 记录归并,成为一个联系人。 当需要修改某联系人相关的所有信息时,这会比较方便。 Contacts Provider 负责创建新的 Contacts 记录,并会把多条 Raw Contact 记录与已有的 Contacts 记录关联。 应用程序和 Sync Adapter 都无权添加 Contacts 记录,而且其中的某些字段还是只读的。
注意:
当试图用
insert()
在 Contacts Provider 中添加联系人记录时,会触发
UnsupportedOperationException
异常。而对“只读”字段的修改将被会忽略。
当某条新 Raw Contact 记录与已有的 Contacts 记录均无法匹配时, Contacts Provider 会创建一条新的 Contacts 记录。 在 Raw Contact 记录被修改之后,如果不再匹配之前的 Contacts 记录了,Contacts Provider 也会创建一条新的 Contacts 记录。 如果应用程序或 Sync Adapter 新建的 Raw Contact 记录确实与已有的 Contacts 记录相匹配,则会建立关联关系。
Contacts Provider 利用
Contacts
表的 _ID
字段将 Contacts 记录与 Raw Contact 记录关联起来。
ContactsContract.RawContacts
表的 CONTACT_ID
字段中存放了相关 Contacts 记录的 _ID
值。
Contacts
表中还包含着一个字段
LOOKUP_KEY
,这是每条记录的“固定”标识。
因为 Contacts 表是由 Contacts Provider 自动维护的,在归并或同步联系人数据时,
_ID
的值可能会被其修改。但是带有
LOOKUP_KEY
的 URI
CONTENT_LOOKUP_URI
仍然会指向原有的 Contact 记录,因此可以用
LOOKUP_KEY
对联系人完成“收藏”等操作。该字段有自己的格式,与
_ID
字段的格式无关。
图 3 给出了三张表之间的关系。
Contacts Provider 中的联系人信息可以是由用户录入的,也可以通过 Sync Adapter 从 Web 服务端插入,
这种服务端和设备之间的数据传输是自动完成的。
Sync Adapter 运行于后台,由系统通过
ContentResolver
进行管理。
在 Android 平台中,与 Sync Adapter 合作的 Web 服务端是由账户类型标识的。 每个 Sync Adapter 与一种账户类型关联,但可以支持同一种类型下的多个账户名称。 账户类型和账户名称已在 Raw Contact 的数据来源 一节中进行过简要介绍了。下面将介绍更多细节,说明账户类型和账户名称是如何与 Sync Adapter 及后台服务相关联的。
google.com
。
AccountManager
将利用这个值来识别相应的账户类型。
账户类型不一定是唯一的。用户可以配置多个 Google Contacts 账户,并把各自的数据下载到 Contacts Provider 中。 这可能是由于他有一些账户是私人使用的,而另一些账户则用于工作。 账户名称通常是唯一的。它和账户类型合在一起,唯一标识了 Contacts Provider 和外部服务之间的一条数据链。
为了能将自有服务的数据传递给 Contacts Provider,需要编写自己的 Sync Adapter。 更多细节将在 Contacts Provider Sync Adapter 一节中介绍。
图 4 演示了 Contacts Provider 在联系人数据流中所起的作用。 在标为“sync adapters”的虚线框中,每个适配器(Adapter)都标出了账户类型。
访问 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">
。
上述权限与用户个人资料(Profile)信息无关。用户个人资料及权限将在 用户个人资料 一节中介绍。
请记住,联系人信息具有私密性和敏感性。 用户都会比较在意自己的隐私,所以肯定不希望应用程序收集本人或其他联系人的信息。 如果没有为访问联系人信息给出充分的理由,用户可能会对该应用给出差评,或者直接拒绝安装。
ContactsContract.Contacts
表中有一条记录保存着当前用户的个人资料。
这条数据描述的是设备用户
,而不是联系人。
这条 Contacts 数据与一条 Raw Contacts 的记录关联,每个带有用户个人资料的系统都会有一条。
每条 Raw Contacts 记录可以对应有多条 Data 表的记录。在
ContactsContract.Profile
类中给出了所有访问用户个人资料时要用到的常量定义。
访问用户个人资料需要特定的权限。除了读写联系人时需要的
READ_CONTACTS
和
WRITE_CONTACTS
权限之外,读写用户个人资料还分别需要
READ_PROFILE
和
WRITE_PROFILE
权限。
请记住,用户个人资料属于敏感数据。拥有
READ_PROFILE
权限可以访问到用户的个人身份信息。请务必在应用程序的描述信息里,说明申请用户个人资料访问权限的理由。
通过调用
ContentResolver.query()
方法,可以获取包含用户个人资料的 Contacts 记录。请把这里的 Content URI 置为
CONTENT_URI
,且不需要给出任何查询条件。
基于该 Content URI ,还可以获取 Raw Contact 记录及用户个人资料。
例如,以下代码段就实现了用户个人资料的读取:
// 设置所要读取的用户个人资料字段 mProjection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.LOOKUP_KEY, Profile.PHOTO_THUMBNAIL_URI }; // 从 Contacts Provider 中读取用户个人资料 mProfileCursor = getContentResolver().query( Profile.CONTENT_URI, mProjection , null, null, null);
注意: 如果读取到多条联系人记录,通过检查 IS_USER_PROFILE
字段,可以确定哪一条是用户个人资料数据。 如果该字段为 1 ,则表示该条记录为用户个人资料。
Contacts Provider 用数据库管理着联系人的数据。 这些元数据(Metadata)分别存放于几张表中,包括 Raw Contacts、Data、Contacts、 ContactsContract.Settings
、 ContactsContract.SyncState
等。每种元数据的作用如下所示:
表名 | 字段 | 值 | 含义 |
---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
“0”表示自前一次同步以来没有变化 | 标记本机 Raw Contact 记录已被修改过并需要同步到服务器端去。 当 Android 应用程序做出修改后,Contacts Provider 会自动设置该字段值。
修改 Raw Contact 或 Data 表的 Sync Adapter 应该确保在 Content URI 后面添加 |
“1”表示前一次同步之后发生了变化,需要将数据向服务器端同步。 | |||
ContactsContract.RawContacts |
VERSION |
本行数据的版本号。 | 只要本行数据或关联记录发生了变化, Contacts Provider 就会自动递增该字段值。 |
ContactsContract.Data |
DATA_VERSION |
本行数据的版本号。 | 只要本条 Data 数据发生了变化,Contacts Provider 就会自动递增该字段值。 |
ContactsContract.RawContacts |
SOURCE_ID |
字符串值,唯一标识了创建该条 Raw Contact 记录的账户。 | 当 Sync Adapter 新建一条 Raw Contact 记录时,本字段就应被置为服务器端给出的唯一 ID。 而当 Android 应用程序新建一条 Raw Contact 记录时,应该将本字段保持为空。 这就意味着, Sync Adapter 应该先在服务器端创建一条 Raw Contact 记录,并为 SOURCE_ID 获取一个值。
有一点特别重要,每种账户类型的 SOURCE_ID 必须唯一,并在同步过程中保持不变:
|
ContactsContract.Groups |
GROUP_VISIBLE |
“0”表示本组联系人不允许显示在 Android 应用程序的界面中。 | 本字段是为了与某些服务器端保持兼容,这些服务端支持隐藏某组联系人的功能。 |
“1”表示本组联系人可由应用程序显示。 | |||
ContactsContract.Settings |
UNGROUPED_VISIBLE |
“0”表示:如果不属于任何组,那么本账户和账户类型的联系人将不会显示在 Android 应用程序界面中。 | 默认情况下,如果联系人的所有“Raw Contact”均不属于任何群组( Raw Contact 的分组关系由 ContactsContract.Data 表中的 ContactsContract.CommonDataKinds.GroupMembership 记录来定义),那么这些联系人是不可见的。通过设置 ContactsContract.Settings 表中的这个字段,可以强制显示某账户类型及账户的未分组联系人。 本标志的一种用途就是把服务器端未分组的联系人显示出来。 |
“1”表示:即使不属于任何组,本账户和账户类型的联系人也可以在 Android 应用程序界面中显示。 | |||
ContactsContract.SyncState |
(所有字段) | 本表供 Sync Adapter 存放元数据。 | 本表可用于在本地持久保存同步状态及其他相关数据。 |
本节介绍了 Contacts Provider 的访问规则,重点包括:
有关由 Sync Adapter 进行数据修改的更多细节,还将在 Contacts Provider Sync Adapter 一节中进行介绍。
因为 Contacts Provider 中的数据表是按照一定的层次结构组织在一起的,所以它非常适用于将一条记录连同所有关联“子”记录一起读取出来。 比如,为了显示某人的所有信息,可能要读取一条 ContactsContract.Contacts
记录对应的所有 ContactsContract.RawContacts
记录,或者是一条 ContactsContract.RawContacts
记录对应的所有 ContactsContract.CommonDataKinds.Email
记录。为了便于操作,Contacts Provider 提出了 实体(Entity)的概念,它类似于关联了多张表的数据库。
一个实体类似于一张表,它由某父表及其子表中的选定字段构成。 在对实体进行查询时,需要根据实体的字段,给出字段映射关系(Projection)和查询条件。 返回的结果是一个 Cursor
(游标),其中每个子表的每条记录都对应着一条记录。 比如,假设查询了一个 ContactsContract.Contacts.Entity
,要得到某个联系人姓名及该姓名下所有 Raw Contact 对应的所有 ContactsContract.CommonDataKinds.Email
记录,则返回的结果中每一行都对应于一条 ContactsContract.CommonDataKinds.Email
记录。
实体简化了查询操作。利用实体可以一次取回某个联系人的所有信息,而不需要先查询父表获取 ID、再用 ID 查询子表了。 而且 Contacts Provider 在处理实体查询时将放入一个事务中来完成,确保了数据的一致性。
注意: 实体通常不会包含父表和子表的全部字段。如果试图对不在实体字段常量列表中的字段进行操作,将会触发异常
。
以下代码段演示了读取某联系人的所有 Raw Contact 记录。 这段代码属于一个拥有两个 Activity “main”、“detail”的应用程序。 Activity “main”将显示联系人列表,当用户选中一个联系人时,Activity 将其 ID 发送给 Activity “detail”。 Activity “detail” 利用 ContactsContract.Contacts.Entity
显示选中联系人的所有 Raw Contact 数据。
这是 Activity “detail” 的部分代码:
... /* * 在 URI 中添加实体路径。 * 对于 Contacts Provider 而言, URI 应为 content://com.google.contacts/#/entity (# 代表 ID)。 */ mContactUri = Uri.withAppendedPath( mContactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); // 初始化 loader 。 getLoaderManager().initLoader( LOADER_ID, // loader ID null, // loader 的参数(这里没有) this); // Activity 的上下文 context // 新建 ListView 要绑定的 Cursor Adapter mCursorAdapter = new SimpleCursorAdapter( this, // Activity 的 context R.layout.detail_list_item, // 包含 detail widget 的 View 项 mCursor, // 处于后台的游标 mFromColumns, // 游标中的数据字段 mToViews, // 显示数据用的 View 0); // 标志 // 设置 ListView 的后台 Adapter mRawContactList.setAdapter(mCursorAdapter); ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { /* * 设置要读取的字段。 * RAW_CONTACT_ID 标识了当前数据行所属的 Raw Contact * DATA1 为 Data 记录中的第一个数据字段(通常存放最重要的数据)。 * MIMETYPE 指明了 Data 记录的数据类型。 */ String[] projection = { ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE }; /* * 根据 Raw Contact 的 ID 对返回游标进行排序, * 以便让同一个 Raw Contact 的所有 Data 记录放在一起。 */ String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; /* * 返回一个新的 CursorLoader。 * 调用参数与 ContentResolver.query() 类似,只是 Context 参数不同,那里是所用的 ContentResolver。 */ return new CursorLoader( getApplicationContext(), // Activity 的 Context mContactUri, // 联系人的实体 Content URI projection, // 要返回的字段 null, // 读取所有 Raw Contact 记录及相关 Data 记录 null, // sortOrder); // 按照 Raw Contact ID 排序 }
载入完成后, LoaderManager
将会调用 onLoadFinished()
。该方法的传入参数中有一个带有查询结果的 Cursor
。然后应用程序就可以从该 Cursor
中读取数据,用于显示或其他用途。
应该尽可能地以“批处理方式”(Batch)进行 Contacts Provider 的增删改操作。 这通过创建由 ContentProviderOperation
组成的 ArrayList
,然后调用 applyBatch()
即可实现。因为 Contacts Provider 会把一个 applyBatch()
中的所有操作放入一个事务中完成,所以不会发生数据不一致的情况。 批量修改也让一次完成 Raw Contact 和明细记录的操作变得更为简单。
注意: 如果要修改单条 Raw Contact 记录,可以考虑向系统自带的联系人应用发送 Intent, 而不用在自己的程序中来完成。 详情请参阅通过 Intent 读写数据。
如果批量修改所包含的操作很多,就会阻塞其他的进程,这样用户体验就会很糟糕。 这时就需要把所有操作尽可能拆分为多个独立的列表,并防止系统的阻塞,这可以通过设置Yield Point来实现。 Yield Point 是一种 ContentProviderOperation
对象,它的 isYieldAllowed()
设为 true
。 当 Contacts Provider 处理到 Yield Point 时,将会暂停操作让其他进程运行,并关闭当前事务。 当再次启动 Contacts Provider 时,它会继续 ArrayList
中的操作,并启动一个新事务。
Yield Point 使得每次 applyBatch()
调用时会建立多个事务。因此,应该把插入 Raw Contact 记录和相关 Data 记录放在一起,再设置一个 Yield Point。 或是把与一个联系人相关的操作组合在一起,再设置一个 Yield Point。
Yield Point 也是一种原子操作单位。两个 Yield Point 之间的操作要么全部成功,要么全部失败。 如果没有设置任何 Yield Point,则最小的原子操作单位就是整个批量任务。 通过 Yield Point 的使用,确实可以防止系统性能的下降,同时也把全部操作拆分为几个原子操作组。
在用一组 ContentProviderOperation
插入 Raw Contact 及相关 Data 记录时, 必须把多条 Data 记录与该 Raw Contact 进行关联,这通过把 RAW_CONTACT_ID
字段值设为该 Raw Contact 的 _ID
即可。但是,在创建插入 Data 记录的 ContentProviderOperation
时,该 ID 值还未就绪,因为这时候插入 Raw Contact 记录的 ContentProviderOperation
还没有提交呢。 为了解决这一问题,可以通过 ContentProviderOperation.Builder
类的 withValueBackReference()
方法。该方法允许利用前一次操作的结果插入或修改字段。
withValueBackReference()
方法的参数有两个:
key
previousResult
ContentProviderResult
对象组成,是由 applyBatch()
生成的。 当执行批量操作时,每步操作的结果都被保存在一个中间结果数组中。 previousResult
即为这些中间结果的索引,可通过 key
进行读写。 这样就可以先插入一条 Raw Contact 记录并得到其 _ID
值,在后续插入 ContactsContract.Data
记录时就可以“向前引用”(Back Reference)该值。
中间结果数组是在第一次调用 applyBatch()
时创建的,数组大小等于由所需 ContentProviderOperation
组成的 ArrayList
大小。不过,该结果数组的所有元素都预置为 null
,如果试图向前引用一个不存在的终结结果, withValueBackReference()
将会抛出 Exception
。
以下代码段演示了批量插入 Raw Contact 及 Data 记录的过程。 其中包含了建立 Yield Point 及使用向前引用的代码。这段代码是 ContactAdder
类的 createContacEntry()
方法的升级版,该类属于 Contact Manager
例程的一部分。
第一段代码将从界面中读取联系人信息。用户这时应该已经选择了要添加 Raw Contact 记录的账户。
// 根据用户界面中的信息,在当前选中账户中创建联系人入口 protected void createContactEntry() { /* * 读取界面中的数据 */ 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());
以下代码创建了一个 Operation 对象,其在 ContactsContract.RawContacts
表中插入一条 Raw Contact 记录。
/* * 准备插入 Raw Contact 记录的批量操作。 * 即便 Contacts Provider 中不存在此联系人的任何数据,也不允许直接添加 Contact 记录, * 而只能添加一条 Raw Contact 记录。 * Contacts Provider 会随后自动生成一条 Contact 。 */ // 新建一个由 ContentProviderOperation 对象组成的队列 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * 创建指定账户类型(服务器类型)和账户名称(用户名)的 Raw Contact 记录。 * 请注意,账户的显示名称并不保存在此记录中,而是存于 StructuredName 记录中。 * 其他数据可以不填。 */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); // 创建操作并加入到队列中 ops.add(op.build());
接下来,创建显示名称、电话号码和 Email 记录。
每个 Builder 对象都通过 withValueBackReference()
获得 RAW_CONTACT_ID
。 这个引用(Reference)指向第一步操作的结果对象 ContentProviderResult
,而第一步操作中添加了 Raw Contact 并返回新生成记录的 _ID
。 这样,每条记录都通过自己的 RAW_CONTACT_ID
字段与所属的新增 ContactsContract.RawContacts
记录关联起来。
ContentProviderOperation.Builder
添加 Email 记录, withYieldAllowed()
标记表示设置一个事务提交点(yield point)。
// 为新 Raw Contact 记录创建显示名称,即一条 StructuredName 记录。 op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference 将第一个参数置为 ContentProviderResult 值, * ContentProviderResult 的索引值由第二个参数给出。 * 在本例中,StructuredName 的 Raw Contact ID 列设为第一步操作返回的结果值, * 这步操作也就是实际添加 Raw Contact 记录的操作。 */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // 把本条记录的 MIME 类型置为 StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // 将本条记录的显示名称设置为用户界面中显示的名字 .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); // 生成操作对象并添加到队列中 ops.add(op.build()); // 插入电话号码,记录类型设置为 Phone 类型 op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * 把 Raw Contact ID 字段置为第一步操作返回的 Raw Contact ID */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // 把 MIME 类型设为 Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // 设置电话号码和类型 .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); // 生成操作对象并添加到队列中 ops.add(op.build()); // 插入 Email 数据,类型设为 Email op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * 把 Raw Contact ID 字段置为第一步操作返回的 Raw Contact ID */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // 把 MIME 类型设为 Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // 设置 Email 值和类型 .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); /* * 演示事务提交点(yield point)。 * 这表示在本次插入操作完成后,批量操作线程将优先于其他线程执行。 * 每对一个联系人完成一组操作后,请设置一个提交点,以避免(维持长事务带来的)性能下降。 */ op.withYieldAllowed(true); // 生成操作并加入操作队列 ops.add(op.build());
最后一段代码演示了 applyBatch()
的调用,以便插入新 Raw Contact 及数据。
// 请求 Contacts Provider 新建一个联系人 Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" + mSelectedAccount.getType() + ")"); Log.d(TAG,"Creating contact: " + name); /* * 批量提交 ContentProviderOperation 队列。 * 忽略返回结果。 */ try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // 显示警告 Context ctx = getApplicationContext(); CharSequence txt = getString(R.string.contactCreationFailure); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(ctx, txt, duration); toast.show(); // 将异常记入日志 Log.e(TAG, "Exception encountered while inserting contact: " + e); } }
批量操作可以实现乐观并发控制(optimistic concurrency control), 这种方式可以不必锁定底层数据即可实现修改事务。 为了使用这种方式,在提交事务后,需要检查可能同时发生的其他修改操作。 如果发现了修改冲突,需要回滚并重新提交。
乐观并发控制在移动设备上非常有用,因为同时只会有一个用户,同时访问一块数据的可能性很小。 因为没有用到锁定机制,就不需要浪费时间加锁,也不需要等待其他事务解锁了。
如果要在更新某条 ContactsContract.RawContacts
数据行时使用乐观并发控制,请按以下步骤执行:
VERSION
字段。newAssertQuery(Uri)
方法创建一个合适的 ContentProviderOperation.Builder
对象。对于 Content URI 而言,使用 RawContacts.CONTENT_URI
,并附带 Raw Contact ID 即可。ContentProviderOperation.Builder
对象的 withValue()
方法,把 VERSION
字段与前面取回的版本号进行对比。ContentProviderOperation.Builder
对象的 withExpectedCount()
方法,确保本次比较只涉及一条记录。build()
方法创建 ContentProviderOperation
对象,并把它作为第一个成员加入列表 ArrayList
中,这个列表是要传给 applyBatch()
的。如果在读写某 Raw Contact 记录期间,其他操作也在更新此记录,“断言”(assert) ContentProviderOperation
将会失败,全部批量操作都会撤销。 后面可以再次提交或者执行其他操作。
以下代码演示了,在用 CursorLoader
查询到一条 Raw Contact 记录后, 如何创建 ContentProviderOperation
“断言”:
/* * 应用程序通过 CursorLoader 查询 Raw Contacts 表。 * 系统将会在加载完成后调用此方法。 */ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // 读取 Raw Contact _ID 和 VERSION 值 mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); } ... // 为断言操作建立 Uri Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID); // 创建断言操作 ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri); // 添加断言:检查版本号、涉及的记录数 assertOp.withValue(SyncColumns.VERSION, mVersion); assertOp.withExpectedCount(1); // 创建 ArrayList 保存 ContentProviderOperation 对象 ArrayList ops = new ArrayList<ContentProviderOperationg>; ops.add(assertOp.build()); // 可往 ops 中添加其他批量操作 ... // 提交批处理操作。如果断言失败,将会抛出异常 try { ContentProviderResult[] results = getContentResolver().applyBatch(AUTHORITY, ops); } catch (OperationApplicationException e) { // 在这里完成断言失败时要执行的动作 }
通过向系统的 Contacts 应用发送 Intent,可以直接访问 Contacts Provider. 该类 Intent 将打开 Contacts 应用的界面,用户可以在里面进行一些联系人信息的操作。 这种方式可以让用户进行:
如果用户需要插入或修改数据,可以先记录这些信息并作为 Intent 的一部分发送出去。
在用 Intent 通过系统 Contacts 应用访问 Contacts Provider 时, 不需要设计用户界面,不需要编写数据访问代码,也不必申请 Provider 读写权限。 Contacts 系统应用可以授予读取联系人的权限, 因为是通过其他应用程序修改 Provider 数据的,所以也不需要拥有写入权限。
发送 Intent 来访问 Provider 的通用步骤在 Content Provider 基础 的“通过 Intent 访问数据”一节中已有详细介绍。 相关操作可用的 Action 的 MIME 类型,以及数据类型都在表4中列出, putExtra()
可用的附件数据都在参考文档 ContactsContract.Intents.Insert
中给出。
操作 | Action | 数据 | MIME 类型 | 备注 |
---|---|---|---|---|
选取联系人 | ACTION_PICK |
|
不需要 | 根据给出的 Content URI 类型,显示 Raw Contact 列表或某个 Raw Contact 的数据列表。
调用 |
插入新 Raw Contact 记录 | Insert.ACTION |
N/A | RawContacts.CONTENT_TYPE ,表示一组 Raw Contact 。 |
显示系统“联系人”应用的新建联系人窗口。 加入 Intent 中的附件数据将一起显示出来。 如果是用 startActivityForResult() 发送的,新 Raw Contact 记录的 Content URI 将会传回给 Activity 的 onActivityResult() 方法,在 Intent 参数的“data”部分中。调用 getData() 即可读取。 |
编辑联系人 | ACTION_EDIT |
联系人的 CONTENT_LOOKUP_URI 。用户可以在编辑器窗口中修改联系人相关的数据。 |
Contacts.CONTENT_ITEM_TYPE ,表示一个联系人。 |
显示 Contacts 应用中的“修改联系人”窗口。加入 Intent 中的附件数据将会一并显示出来。 用户点击保存按钮保存数据时,调用者的 Activity 将会回到前台。 |
显示可添加数据的选择列表 | ACTION_INSERT_OR_EDIT |
N/A | CONTENT_ITEM_TYPE |
这个 Intent 总是显示 Contacts 应用的选择界面。 用户可以选中某个联系人进行编辑,也可以添加新的联系人。 到底是显示编辑还是添加界面,取决于用户的选择,以及用 Intent 附件显示的信息。 如果调用方显示的是联系人的相关数据,比如 Email 或电话号码,则可利用此 Intent 让用户为已有联系人添加数据。
注意:在此类 Intent 附件中不需要发送姓名, 因为用户要么是从已有姓名中选取一个,要么就是添加新用户。 而且,假如发送了姓名且用户选择了编辑联系人,则 Contacts 应用会显示发送过去的姓名,之前的姓名会被覆盖。 如果用户没注意到这一点,又进行了保存,则以前的姓名就丢失了。 |
系统“联系人”应用不允许通过 Intent 删除 Raw Contact 及相关数据。 要想删除 Raw Contact 记录,请使用 ContentResolver.delete()
或ContentProviderOperation.newDelete()
。
以下代码演示了如何建立并发送一个插入 Raw Contact 及数据的 Intent:
// 读取用户界面中的数据 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(); // 新建一个发送给系统“联系人”应用的 Intent Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); // 把 MIME 类型置为所需的插入记录 Activity insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); // 设置联系人姓名 insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); // 设置公司名称和职位 insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); /* * 以列表方式添加各数据行,相互以 DATA 键关联 */ // 定义 ContentValues 对象列表,每个对象对应一行数据 ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); /* * 定义 Raw Contact 行 */ // 新建 ContentValues 对象作为行数据 ContentValues rawContactRow = new ContentValues(); // 加入账户类型和名称 rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()); rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); // 把此行添加到列表中 contactData.add(rawContactRow); /* * 建立电话号码数据行 */ // 新建 ContentValues 对象作为行数据 ContentValues phoneRow = new ContentValues(); // 设定 MIME 类型(所有数据行都必须给定类型) phoneRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ); // 加入电话号码及其类型数据 phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); // 把此行添加到列表中 contactData.add(phoneRow); /* * 建立 Email 数据行 */ // 新建 ContentValues 对象作为行数据 ContentValues emailRow = new ContentValues(); // 设定 MIME 类型(所有数据行都必须给定类型) emailRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ); // 加入 Email 及其类型数据 emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); // 把此行添加到列表中 contactData.add(emailRow); /* * 把上述列表添加到 Intent 的附件中。 * 这个列表必须是可序列化的(parcelable),以便能在进程间传递。 * 系统应用 Ccontacts 需要把 Intents.Insert.DATA 内容作为键值使用。 */ insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); // 发送 Intent,启动系统“联系人”应用并打开新建联系人 Activity。 startActivity(insertIntent);
因为联系人数据比较重要和敏感,用户希望能及时修正,所以 Contacts Provider 规定了一些保证数据完整性的规则。 在修改联系人信息时,必须遵守这些规则。 下面给出一些比较重要的规则:
ContactsContract.RawContacts
记录都添加一条 ContactsContract.CommonDataKinds.StructuredName
记录。ContactsContract.RawContacts
数据行没有在 ContactsContract.Data
表中存在对应的 ContactsContract.CommonDataKinds.StructuredName
记录,可能会引发问题。ContactsContract.Data
链接到上级 ContactsContract.RawContacts
记录中。ContactsContract.RawContacts
记录的 ContactsContract.Data
数据行不会在系统“联系人”应用中显示,并可能会在使用 Sync Adapter 时引发问题。TYPE
时,确保使用 ContactsContract
及其子类中定义的常量,通过创建并应用自定义的 MIME 类型,可以在 ContactsContract.Data
表中插入、编辑、删除和读取自定义数据。 虽然可以把自定义类型的字段名映射为默认字段名称,自定义数据行只能使用 ContactsContract.DataColumns
中给定的字段。在系统“联系人”应用中,自定义数据可以正常显示,但无法编辑和删除,用户也不能加入其他信息。 如果要让用户能修改自定义数据行,必须自行提供编辑 Activity。
为了显示自定义数据,需要提供一个 contacts.xml
文件,里面包含一个 <ContactsAccountType>
元素,其内部包含一个以上的 <ContactsDataKind>
子元素。 详情请参阅 <ContactsDataKind>
元素 一节。
关于自定义 MIME 类型的更多细节,请参阅 创建 Content Provider。
为了完成设备和在线服务之间的联系人数据同步(synchronization),Contacts Provider 经过了特别设计。 这样,用户就可以把已有数据下载到新设备上,还可以把数据上传到新建账户中去。 同步可以确保用户手头的设备拥有最新的数据,不管是由于添加还是修改引起的。 同步的另一个好处就是,在网络断开时设备也能使用联系人信息。
虽然实现同步的方式可以有很多种,但 Android 系统提供了一种插件式(plug-in)的同步框架,可以自动完成以下工作:
使用这一框架时,需要给出 Sync Adapter 插件。 每个 Aync Adapter 都唯一对应一个服务和一个 Content Provider,但可以处理同一个服务里的多个账户名。 此框架还允许同一套服务和 Provider 使用多个 Sync Adapter。
Sync Adapter 都实现为 AbstractThreadedSyncAdapter
的一个子类,并要作为 Android 应用的一部分进行安装。 系统会从 Manifest 文件中获取 Sync Adapter 的信息,并读取由 Manifest 文件给出的 XML 文件。 这个 XML 文件定义了在线服务的账户类型,以及 Content Provider 的用户认证信息,这些都是该 Adapter 的唯一标识。 Sync Adapter 一开始并不会运行,只有当用户添加 Sync Adapter 中账户类型的账户,并开启其对应 Content Provider 的同步时,它才会被激活。 这时,系统会负责管理 Adapter,适时调用它完成 Content Provider 和服务器之间的同步。
注意: 把账户类型作为 Sync Adapter 唯一标识的一部分,可以让系统对它们进行分组,把访问同一公司服务的 Sync Adapter 放在一起。 比如,Google 在线服务的 Sync Adapter 都具有相同的账户类型 com.google
。 当用户在设备上添加 Google 账户时,所有已安装的 Google 服务 Sync Adapter 都会显示在一起; 每个 Sync Adapter 列出各自关联的本地 Content Provider。
因为绝大多数服务都需要在访问数据之前验证用户身份,Android 系统提供了一种与 Adapter Adapter 框架类似的,并与其协同工作的用户认证框架。 这种认证框架使用了插件式的 Authenticator,它是 AbstractAccountAuthenticator
的一个子类。Authenticator 验证用户身份的步骤如下:
如果服务接受了用户信息,Authenticator 可以保存这些信息以备将来使用。 因为认证框架是插件式的, AccountManager
能够访问所有可支持的令牌(authtoken),并选择其公开性,比如 OAuth2 令牌。
虽然用户认证过程不是必需环节,但大部分与“联系人”相关的服务都会要求使用。 当然,这不一定非要用 Android 认证框架来完成。
为了给 Contacts Provider 编写一个 Sync Adapter,请先创建一个包含以下部分的 Android 应用程序:
Service
组件,响应系统发出的绑定 Sync Adapter 的请求。onBind()
方法来获取一个 Sync Adapter 使用的 IBinder
。这使得系统可以跨进程调用 Adapter 的方法。
在Sync Adapter 范例 中,服务的类名为 com.example.android.samplesync.syncadapter.SyncService
。
AbstractThreadedSyncAdapter
的实体类。onPerformSync()
方法中。此类必须实现为单实例。
在 Sync Adapter 范例 中,Sync Adapter 定义在 com.example.android.samplesync.syncadapter.SyncAdapter
中。
Application
的子类。onCreate()
方法实例化 Sync Adapter,并提供静态方法“getter”用于向 onBind()
方法返回该单实例。 service.Service
组件。AccountManager
启动此服务开始认证过程。该服务的 onCreate()
方法实例化一个 Authenticator 对象。 当系统需要认证 Sync Adapter 所用的用户账户时,将会调用其 onBind()
方法来获得一个 IBinder
。 这样系统就能跨进程调用 Authenticator 的方法了。
在 Sync Adapter 范例 中,服务的类名为 com.example.android.samplesync.authenticator.AuthenticationService
。
AbstractAccountAuthenticator
的实体子类,用于处理认证请求。AccountManager
调用的方法,用来与服务器端进行身份认证。 由于服务器端采用的技术不同,各种认证过程的细节差别很大。 关于用户认证的更多内容,请参考服务器端所用软件的文档。
在Sync Adapter范例 中,Authenticator 在 com.example.android.samplesync.authenticator.Authenticator
类中定义。
<service>
元素中。这些元素包含了以下一些 <meta-data>
子元素,用于向系统报告相应的数据信息:
ContactsContract.StreamItems
和 ContactsContract.StreamItemPhotos
表管理着来自社交网络的数据。 可以编写一个 Sync Adapter,把来自个人社交网络圈的数据添加到这两张表中去,或者从表中读取社交数据并显示出来。 通过这种方式,可以把自己的社交网络后台服务和前台应用,与 Android 的社交网络用户体验集成在一起。
社交流数据项必须与某个 Raw Contact 关联。 RAW_CONTACT_ID
即为 Raw Contact 的 _ID
值。 Raw Contact 的账户类型和账户名称也会保存在社交流数据记录中。
社交流数据保存在以下字段中:
ACCOUNT_TYPE
ACCOUNT_NAME
CONTACT_ID
:本条数据关联的联系人 _ID
。CONTACT_LOOKUP_KEY
:本条数据关联的联系人 LOOKUP_KEY
。RAW_CONTACT_ID
:本条数据关联的 Raw Contact _ID
。COMMENTS
TEXT
fromHtml()
解析的图片资源。Provider 可能会截断或略去超长的文本,但会尽量避免在语言标记(tag)中间截断。TIMESTAMP
为了能醒目地显示社交流数据,会用到 RES_ICON
、 RES_LABEL
和 RES_PACKAGE
,这些都代表着应用程序的资源。
ContactsContract.StreamItems
表还包含 SYNC1
至 SYNC4
字段,用于 Sync Adapter 间的互斥同步。
ContactsContract.StreamItemPhotos
表存放着社交流数据项关联的图片信息。并通过 STREAM_ITEM_ID
字段与 ContactsContract.StreamItems
表的 _ID
字段关联。图片的引用方式保存在以下字段中:
PHOTO
字段(BLOB类型)。PHOTO_FILE_ID
或 PHOTO_URI
(都在下一节介绍)保存到文件中。 目前该字段用于存放图片的缩略图,以供读取。PHOTO_FILE_ID
DisplayPhoto.CONTENT_URI
常量之后,即为指向某个图片文件的 Content URI,然后调用 openAssetFileDescriptor()
即可获得图片文件的句柄。PHOTO_URI
openAssetFileDescriptor()
可以获得图片文件的句柄。上述表的使用方式与 Contacts Provider 中的其他主表基本相同,以下几点除外:
READ_SOCIAL_STREAM
权限。修改时需要 WRITE_SOCIAL_STREAM
权限。ContactsContract.StreamItems
表中对应的记录数是有限制的。 如果到达上限,Contacts Provider 会自动删除 TIMESTAMP
最早的记录,以便为新进的社交流数据腾出空间。 用 Content URI 为 CONTENT_LIMIT_URI
进行数据库查询,可以获取记录数上限值,其他参数都置为 null
即可。该查询会返回包含一条记录的 Cursor,且只有一个字段 MAX_ITEMS
。ContactsContract.StreamItems.StreamItemPhotos
类定义了 ContactsContract.StreamItemPhotos
的子表,里面存放着某条社交流数据相关的图片数据记录。
Contacts Provider 管理的社交流数据,连同系统“联系人”应用一起, 可以将社交网络系统与现有的联系人连接起来,得以实现以下强大功能:
ContactsContract.StreamItems
和 ContactsContract.StreamItemPhotos
表中,以备后用。Contacts Provider 与社交流数据的定期同步功能与其他同步是一样的。 关于同步的更多细节,请参阅 Contacts Provider Sync Adapter。 下面两节将介绍如何注册通知和邀请联系人。
当用户查看由 Sync Adapter 管理的联系人时,为了能接收到通知,需要注册 Sync Adapter。步骤如下:
res/xml/
目录中,创建名为 contacts.xml
的文件. 如果该文件已存在,可以跳过此步。<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
元素。如果此元素已存在,则跳过此步。viewContactNotifyService="serviceclass"
属性, serviceclass
是服务类的完全限定格式名称,该服务用于接收来自系统“联系人”应用的 Intent。 对于通知(notifier)服务,可以使用 IntentService
的子类,以便服务接收到 Intent。 接收到的 Intent 中包含了用户所选 Raw Contact 的 Content URI。 可以绑定该通知服务,并调用 Sync Adapter 更新该 Raw Contact 的数据。如果需要在用户点击某个社交数据或图片时,调用某个 Activity,请按以下步骤注册 Activity:
res/xml/
目录下,创建名为 contacts.xml
的文件. 如果该文件已存在,可以跳过此步。<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
元素。如果此元素已存在,则跳过此步。viewStreamItemActivity="activityclass"
属性, activityclass
是 Activity 类的完全限定格式名称, 该 Activity 将用于接收来自系统“联系人”应用的 Intent。viewStreamItemPhotoActivity="activityclass"
属性, activityclass
i是 Activity 类的完全限定格式名称, 该 Activity 将用于接收来自系统“联系人”应用的 Intent。关于 <ContactsAccountType>
元素的更多细节,将在 <ContactsAccountType> 元素 一节中介绍。
接收到的 Intent 将包含用户点击的社交流数据项或图片的 Content URI。 如果需要对文本数据和图片分为两个 Activity 进行处理,可以在一个文件中同时使用两个属性。
不需要离开系统的“联系人”应用,用户就可以邀请某个联系人加入自建的社交网站。 只要让系统“联系人”应用向自建 Activity 发送一个邀请 Intent 即可。 请按以下步骤操作:
res/xml/
目录下,创建名为 contacts.xml
的文件. 如果该文件已存在,可以跳过此步。<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
元素。如果此元素已存在,则跳过此步。inviteContactActivity="activityclass"
inviteContactActionLabel="@string/invite_action_label"
activityclass
是接收 Intent 的 Activity 的完全限定类名。 invite_action_label
是个字符串,将在系统“联系人”应用的 Add Connection (译者注:没找到,难道是分享?)菜单中显示。注意: ContactsSource
是过时的标记,现在对应的是 ContactsAccountType
。
contacts.xml
文件包含了一些 XML 元素,用于控制自建 Sync Adapter、自建应用,与系统“联系人”应用、Contacts Provider 之间的交互。 下面介绍这些元素:
<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
定义的 Activity。 比如可以用“Follow in my network”。 这里可以使用字符串资源 ID。viewContactNotifyService
NotifierService.java
文件。viewGroupActivity
viewGroupActionLabel
例如,假定本地已安装了 Google+ 应用,且通过“联系人”应用对 Google+ 数据进行了同步, 则在“联系人”应用的群组(Groups)选项卡中,将会看到 Google+ “圈子”以群组的形式显示出来。 如果点击 Google+ 圈子,就能看到以群组的方式显示的圈子人员。 Google+ 图标会显示在顶部,点击图标则会跳转到 Google+ 应用中去。 “联系人”应用就是通过 viewGroupActivity
来完成上述操作的,并把 viewGroupActionLabel
的值赋为 Google+ 的图标。
这里可以使用字符串资源 ID。
viewStreamItemActivity
viewStreamItemPhotoActivity
<ContactsDataKind>
元素控制着自建应用的自定义数据行在“联系人”应用中的显示方式。 语法如下:
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
包含于:
<ContactsAccountType>
描述:
“联系人”应用可以利用此元素,把自定义数据行作为 Raw Contact 的明细数据之一,一起显示出来。 <ContactsAccountType>
的每个 <ContactsDataKind>
子元素, 代表一种自定义数据行类型,此条数据是由自建 Sync Adapter 向 ContactsContract.Data
表添加的。每个自定义 MIME 类型都需要添加一个 <ContactsDataKind>
元素。 如果自定义数据行的数据不需要显示出来,则无需添加该元素。
属性:
android:mimeType
ContactsContract.Data
表中。例如,记录联系人最新地理位置的数据行,就可用 vnd.android.cursor.item/vnd.example.locationstatus
作为 MIME 类型。android:icon
android:summaryColumn
android:detailColumn
。android:detailColumn
android:summaryColumn
。除上述主要功能外,Contacts Provider 还提供了以下用于处理联系人数据的功能:
Contacts Provider 可以把某些联系人标记为群组。 如果某个账户对应的服务器需要维护群组,该账户类型对应的 Sync Adapter 应负责 Contacts Provider 和服务器之间的群组信息传递工作。 当用户向服务器添加一个新联系人,并把他归入一个新组时,Sync Adapter 必须将这个新组添加到 ContactsContract.Groups
表中。 Raw Contact 所属的一个或多个组使用 ContactsContract.CommonDataKinds.GroupMembership
MIME 类型存储在 ContactsContract.Data
表内。
如果自建 Sync Adapter 会将服务器中的 Raw Contact 数据添加到 Contacts Provider 中,且没有用到群组, 那么需要告诉 Provider 显示这部分数据。 请在处理添加账户操作的代码中,修改账户对应的 ContactsContract.Settings
记录,这条记录是由 Contacts Provider 添加的。 把该行数据的 Settings.UNGROUPED_VISIBLE
字段值置为1即可。这样,即便未用到群组功能,Contacts Provider 也会让相关的联系人数据保持可见。
图片信息以记录的形式保存在 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 的主图片记录,查询条件为 Raw Contact 的 _ID
、 Photo.CONTENT_ITEM_TYPE
和 IS_PRIMARY
字段。
社交流数据本身也可能包含图片。这些图片都保存在 ContactsContract.StreamItemPhotos
表中,社交流图片一节对该表进行了详细介绍。
【最后一篇API译文】Android开发-API指南- Contacts Provider
标签:
原文地址:http://www.cnblogs.com/popapa/p/android_contacts-provider.html