Contacts Provider Access


这一章节讲述了如何从Contacts Provider访问数据。主要关注以下几点:

  • Entity queries.实体查询
  • Batch modification.批量修改
  • Retrieval and modification with intents.使用intents检索和修改
  • Data integrity.数据完整性
在Contacts Provider Sync Adapters中有更多关于使用sync adapter修改数据的详细说明。

Querying entities

由于Contacts Provider的数据表是层次结构组织的,所以经常会需要查询一行和关联到这一行的子数据行。例如,要显示一个联系人的所有信息,你需要去查询多个ContactsContract.RawContacts关联到ContactsContract.Contacts的数据,或者所有管理到 ContactsContract.RawContacts的ContactsContract.CommonDataKinds.Email数据。为了便于实现这个功能,Contacts Provider 提供了实体(entity)的数据结构,它像一个连接各个数据表的数据结构。
一个实体(entity)像一个数据表,它包含从一个父表和它的子表中的多个列。当你查询一个实体(entity),需要提供查询列和查询条件。它返回的结果是一个包含各个子表的Cursor。例如,如果要查询一个人的名字和他所有的邮件地址,使用ContactsContract.Contacts.Entity会得到一个包含所有emai的Cursor。
实体(entities)方便了查询。使用实体(entity)查询,可以一次性得到联系人的所有Data数据和raw contact数据。不需要先查附表得到一个ID,再由这个ID区查询字表。Contacts Provider在一个transaction处理entity的查询,这样能保持数据检索的内部一致性。

注意:一个实体通常没有包含父表和子表所有的列。如果访问到实体没有的列,就会得到一个异常。

下面的代码片段展示了如何检索一个contact的所有raw contact。这个代码片段是一个拥有两个activity的大程序的一部分,这两个activity是“主要”和“详细信息”的activity。“主要”的activity展示一列联系人的数据,当用户选择一个联系人时,把它的ID传给“详细信息”的activity。详细信息的activity使用ContactsContract.Contacts.Entity 展示了改contact的所有Data数据。

下面的代码是“详细信息”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 initializenull,       // Arguments for the loader (in this case, none)this);      // The context of the activity// Creates a new cursor adapter to attach to the list viewmCursorAdapter = new SimpleCursorAdapter(this,                        // the context of the activityR.layout.detail_list_item,   // the view item containing the detail widgetsmCursor,                     // the backing cursormFromColumns,                // the columns in the cursor that provide the datamToViews,                    // the views in the view item that display the data0);                          // 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 contextmContactUri,              // The entity content URI for a single contactprojection,               // The columns to retrievenull,                     // Retrieve all the raw contacts and their data rows.null,                     //sortOrder);               // Sort by the raw contact ID.
}

数据查询完成后, LoaderManager 回调onLoadFinished()。查询的结果会由一个Cursor参数传递。你可以从这个Cursor获取数据用来显示或其他操作。

Batch modification

尽可能使用批量修改来插入、更新和删除Contacts Provider中的数据。通过创建存储ContentProviderOperation的 ArrayList,再调用applyBatch()的方法。因为Contacts Provider在一个transaction中执行applyBatch()的所有操作,所以能够保持数据的一致性。批量操作也可以方便插入一个联系人数据及其详细数据。

注意:如果只是要改一行raw contact的数据,首先考虑用发intent给联系人应用的方式,而不是自己去操作数据。使用intent的方式在Retrieval and modification with intents有更详细的说明。

Yield points

批量修改包含大量的操作可能会阻塞进程,导致用户体验不好。为了把修改操作放在尽可能少的操作列表里面,并且要防止阻塞进程,可以为一个或多个操作设置yield points。 yield point 就是isYieldAllowed()为true的ContentProviderOperation对象。当Contacts Provider执行到yield point的时候,它暂停操作,让其他进程先执行,并关闭当前的操作。当provider重新开始工作后,它继续之前的工作,从操作列表中取出下一个操作,开启一个新的transaction。

Yield point导致调用applyBatch()的时候,会有多个transaction。yield point要设置在一系列相关行操作的最后一个操作。例如,在添加联系人的时候,yield point要设置在添加raw contact和它的data 的最后一个操作,或者添加在一系列跟同一个联系人相关的操作中的最后一个操作。

yield point也是一个原子操作。所有在两个yield point直接操作,在一次操作中要么全部成功,要不全部无效。如果没有设置yield point,整个批量处理就是一个最小的原子操作。如果设置了原子操作,你避免降低系统性能作,而在同一时间,确保操作的子集是原子。

Modification back references

添加一个新的raw contact和它的data数据,使用一系列的ContentProviderOperation对象,你必须把data数据的RAW_CONTACT_ID设置成新添加的raw contact的ID。然而,当创建data数据行的时候,raw contact的ID还没生成。因为这一系列的ContentProviderOperation对象是一个原子操作,还不能得到创建raw contact的结果。为了解决这个问题,ContentProviderOperation.Builder有一个withValueBackReference()的方法。这个方法可以让你使用前面操作的结果来插入数据。

withValueBackReference()有两个参数: 

key    key-value键值对的键。值应该是所修改的表的列名。previousResult     (简单来说,就是操作的索引值)    applyBatch()返回值ContentProviderResult数组的索引。开始批量操作后,每个操作的结果都存在一个数组里。previousResult操作结果数组的一个索引,通过key值检索和存储。这样子就可以在插入一个新的raw contact联系人后,得到它的_ID,然后再你插入Data数据的时候,就可以通过“后引用”去使用这个ID。    在调用applyBatch()的时候,整个结果数组就创建好了,结果数组的大小和ContentProviderOperation的数组大小一样。但是,结果数组的初始值都是null,如果在操作结束前,通过“后引用”来使用操作的结果,就会抛出异常。下面的代码片段显示如何使用batch插入一个raw contact和数据。这些代码包括使用yield points和“后引用”。这个代码片段是createContacEntry()方法的扩展,是Contact Manager  例子源码中的ContactAdder的一部分。第一个源码片在UI中获取联系人的data数据。用户已经选择了用来添加联系人的账户:

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

下一个源码片段创建一个operation去插入一个raw contact数据到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 operationsops.add(op.build());

接下来的代码,创建了显示名字,电话号码和邮箱的data数据。每一个操作使用 withValueBackReference() 去获取RAW_CONTACT_ID。这个引用指向 第一个操作的ContentProviderResult,第一个操作是创建raw contact并返回它的_ID。这样,每个新建的data数据,就自动连接它的 RAW_CONTACT_ID到新插入的ContactsContract.RawContacts 。

ContentProviderOperation.Builder在添加email的时候,使用 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 operationsops.add(op.build());// Inserts the specified phone number and type as a Phone data rowop =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 operationsops.add(op.build());// Inserts the specified email and type as a Phone data rowop =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 operationsops.add(op.build());

最后一个代码片段调用 applyBatch()去插入一个新的raw  contact 和它的data数据行。

// Ask the Contacts Provider to create a new contactLog.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 warningContext ctx = getApplicationContext();CharSequence txt = getString(R.string.contactCreationFailure);int duration = Toast.LENGTH_SHORT;Toast toast = Toast.makeText(ctx, txt, duration);toast.show();// Log exceptionLog.e(TAG, "Exception encountered while inserting contact: " + e);}
}

批量操作允许实现一个优化多线程控制(  optimistic concurrency control),一个无需锁定底层数据库的方法。使用这个方法,在调用transaction 的时候,检查一下是否有其他的修改会同时发生。如果发现同时修改,就取消这次修改。

Optimistic concurrency control 对手机设备很有用,手机设备只有一个用户的,并且很少同时访问数据库。因为数据库没有加锁,就不用花费时间去加锁和解锁。

以下是使用ContactsContract.RawContacts 的步骤:

  1. 在检索数据的时候,同时检索raw contact的VERSION列。
  2. 创建一个ContentProviderOperation.Builder,调用newAssertQuery(Uri)方法,content URI是RawContacts.CONTENT_URI 和raw contact的_ID.
  3. ContentProviderOperation.Builder调用 withValue()的方法去对比刚刚检索到的 VERSION值
  4. 在同一个 ContentProviderOperation.Builder中调用 withExpectedCount() 去确保在这次断言中只有一个修改。
  5. 调用Call build()创建 ContentProviderOperation,然后把这个ContentProviderOperation加入到调用 applyBatch()操作的第一个操作。
  6. 调用批量操作。

如果raw contact在你读取数据,然后去修改它的期间被更新, ContentProviderOperation 将会失败,然后整个力量操作都会失效。这是需要重新尝试这次操作。

下面的代码片段展示如何在使用CursorLoader查询一个raw contact后,创建一个断言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 valuesmRawContactID = 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}

API Guides Contacts Provider (三)相关推荐

  1. android api 完整通译之Contacts Provider (学习安卓必知的api,中英文对照)

    android api 完整翻译之Contacts Provider (学习安卓必知的api,中英文对照) Contacts Provider 电话簿(注:联系人,联络人.通信录)提供者 ------ ...

  2. Android Content Providers(三)——Contacts Provider

    接着上篇Android Content Providers(二)--Contacts Provider继续,接下来要说明的是顶层的Contacts,Contacts是聚合联系人表,在之前讨论的RawC ...

  3. Android API 指南 - Android API Guides

    http://developer.android.com/intl/zh-CN/guide/components/index.html ) 请找到自己要翻译的部分,按下面的'链接规范'添加到下面列表中 ...

  4. Altium Designer 20 Grids、 Guides、 Axes 三种栅格的讲解

    Altium Designer 20 Grids. Guides. Axes 三种栅格的讲解 一.Grids栅格 是我们常用的.默认的普通栅格 Grids栅格常用的设置有: 1.快捷键 G 可以设置栅 ...

  5. 前端js调用后端API获取数据的三种方法(2022.7.25)

    前端js调用后台API获取数据的三种方法(2022.7.25) 前言 需求分析 一个Get实例 浏览器请求 SoapUI软件请求 一个Post实例 浏览器请求 SoapUI软件请求 1.Http简介( ...

  6. Android API Guides 安卓API指导----第一部分:Introduction(介绍)

    第一部分: Introduction(介绍) 目录包含:APP  Fundamentals     (应用程序基础) Device   Compatibility(设备兼容性) System Perm ...

  7. [翻译]API Guides - Bound Services

    官方文档原文地址:http://developer.android.com/guide/components/bound-services.html 一个Bound Service是一个客户端-服务器 ...

  8. WEB API新增整理(三)

    一.Gamepad API 简介: 给予开发者一种简单.统一的方式来识别并响应游戏控制器(手柄).其中包含了三个接口.两个事件.一个特殊函数,用来响应控制器的连接与断开.获取其他关于控制器的信息以及识 ...

  9. 大数据早报:谷歌Cloud Natural Languages API推新技术 全球三分之二的人口通过移动设备上网(9.21)

    数据早知道,上36dsj看早报! 来源36大数据,作者:奥兰多 『谷歌』谷歌Cloud Natural Languages API推新技术:助力新闻行业发展 近日,谷歌在其免费的 Cloud Natu ...

  10. C++ API 设计 08 第三章 模式

    第三章 模式 前一章所讨论的品质是用来区分设计良好和糟糕的API.在接下来的几个章节将重点关注构建高品质的API的技术和原则.这个特殊的章节将涵盖一些有用的设计模式和C++ API设计的相关习惯用法. ...

最新文章

  1. 【WEB API项目实战干货系列】- API登录与身份验证(三)
  2. centos6.5下载卸载mysql_Linux CentOS 6.5 卸载、tar安装MySQL
  3. oracle timestamp比较大小_ORACLE包和过程依赖关系测试
  4. 基于SRS的RTMP分发技术方案
  5. 0810 - 代码,还是得一行行写
  6. 【入门1】顺序结构 (今天刷洛谷了嘛)
  7. switch一定比if else好用吗
  8. git push本地代码到github出错
  9. 极客大学架构师训练营 秒杀 搜索引擎 爬虫 Lucene Elastic Search 第18课 听课总结
  10. ASO优化方法_获取ASO关键词指数接口
  11. ubuntu复制粘贴
  12. DarkComet RAT简介
  13. gsva gsea ssgsea gaochao 使用GSVA方法计算某基因集在各个样本的表现
  14. 服务器系统盘40g是什么,云服务器 40g系统盘
  15. 大数据研究的若干科学问题——徐宗本
  16. 数据链路层的封装-HDLC协议
  17. SLAM算法大体流程---理论知识(入门级)
  18. 裴蜀定理(贝祖定理)
  19. String.Format将人民币符号改成美元符号{0:C}
  20. C语言中猜数大小的实验报告,猜数字游戏实验报告

热门文章

  1. 迅雷 iOS 版终于复活,不限速,完美支持BT磁力下载
  2. 中国首座!智慧机场建成投运,BIM技术打造数字底图
  3. 财务系统需求分析 用户分析 功能需求
  4. bert模型使用记录
  5. 读《About Face 4 交互设计精髓》4
  6. Maven无法加载ojdbc14.jar的解决方法
  7. matlab常用代码及操作
  8. Linux(Ubuntu 14.04) 罗技(logitech) G29 游戏方向盘数据解析(支持自定义开发)
  9. 解析DATASTAGE导出文件dsx和congnos的mdl文件
  10. 专接本C语言必背程序