应用场景

项目中存在这样的需求,通过设备之间通过蓝牙传输联系人,并且需要将获取过来的联系人插入到ContactsProvider中

批量插入联系人的标准代码

在Android的源码中,ContactsContract.java中为我们展示了批量插入联系人的方法。

 The batch method is by far preferred.  It inserts the raw contact and itsconstituent data rows in a single database transactionand causes at most one aggregation pass.<pre>ArrayList<ContentProviderOperation> ops =new ArrayList<ContentProviderOperation>();...int rawContactInsertIndex = ops.size();ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI).withValue(RawContacts.ACCOUNT_TYPE, accountType).withValue(RawContacts.ACCOUNT_NAME, accountName).build());ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI).withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE).withValue(StructuredName.DISPLAY_NAME, &quot;Mike Sullivan&quot;).build());getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

应用实例

在项目联系人列表一开始是存在如下的一个本地联系人列表中

public class TsContact implements Parcelable {public String mPhoneNumber;public int mPhoneType;public String mFirstName;public String mLastName;public String mMiddlename;...
}

假如List < TsContact> contactsList中存放了联系人列表,那么如何将这个TsContacts列表同步到ContactsProvider中呢?
我一开始采用的代码如下:

private void syncTSContactsToContactsProvider(List<TsContact> contactsList) {ArrayList<ContentProviderOperation> ops = new ArrayList<>();for (int index = 0; index < contactsList.size(); index++) {TsContact contact = contactsList.get(index);int rawContactInsertIndex = ops.size();ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null).withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.mLastName).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.mFirstName).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.mMiddlename).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contact.mPhoneNumber).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, contact.mPhoneType).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "").withYieldAllowed(true).build());}try {getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);ops.clear();} catch (final TransactionTooLargeException e) {e.printStackTrace();} catch (final RemoteException e) {LogUtil.e(TAG, "RemoteException in commit");e.printStackTrace();} catch (final OperationApplicationException e) {LogUtil.e(TAG, "OperationApplicationException in commit");e.printStackTrace();}
}

大功告成,运行成功,很开心。效率也比单个插入高很多。

遇到了TransactionTooLargeException

代码运行了一段时间以后,测试提了一个bug,如果联系人很多的时候,无法更新联系人。
查了一下,当联系人列表过大的时候,比如说超过一千,会抛出异常:TransactionTooLargeException。这是因为我们使用applyBatch接口来插入数据,最终还是需要通过binder将这些数据传递给ContactsProvider。而binder是轻量级跨进程通信机制,其传递数据上限。在Android 5.0中其定义为:
BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
那么当联系人列表过大,则传递的数据超过了binder所能传递数据的上限,抛出了异常。
怎么解决这个问题呢?那就是把联系人列表截断,分多次传输。

错误的分批批处理

一开始的我想到的是仍然将联系人列表一次性转化为插入操作的列表ArrayList< ContentProviderOperation > ops,再执行applyBatch,如果遇到TransactionTooLargeException,则将插入列表截成两段,重新插入,如果还有异常,继续截成两段,就这样二分下去,肯定可以插入 。恩,是的perfect code。

那么代码就变成了

private void syncTSContactsToContactsProvider(List<TsContact> contactsList) {ArrayList<ContentProviderOperation> ops = new ArrayList<>();for (int index = 0; index < contactsList.size(); index++) {TsContact contact = contactsList.get(index);int rawContactInsertIndex = ops.size();ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null).withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.mLastName).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.mFirstName).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.mMiddlename).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contact.mPhoneNumber).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, contact.mPhoneType).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "").withYieldAllowed(true).build());}int transactionSize = ops.size();ArrayList<ContentProviderOperation> subOps = new ArrayList<>();while (!ops.isEmpty()) {subOps.clear();if (transactionSize > ops.size()) {transactionSize = ops.size();}subOps.addAll(ops.subList(0, transactionSize));try {getContentResolver().applyBatch(ContactsContract.AUTHORITY, subOps);ops.removeAll(subOps);} catch (final TransactionTooLargeException e) {// If the transaction is too large, try splitting it.if (transactionSize == 1) {Log.e(TAG, "Single operation transaction too large");}Log.d(TAG, "Transaction operation count %d too large, halving..." + transactionSize);transactionSize = transactionSize / 2;if (transactionSize < 1) {transactionSize = 1;}} catch (final RemoteException e) {Log.e(TAG, "RemoteException in commit");e.printStackTrace();} catch (final OperationApplicationException e) {Log.e(TAG, "OperationApplicationException in commit");e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}ops.clear();
}

恩,写完很满意,但是当ops被分成几段,多次处理以后,只有第一次的插入是有效,后面的插入都是无效的了。

正确的批处理的插入姿势

之所以ArrayList< ContentProviderOperation > ops被截断 以后的插入,后面的插入都失败了,是因为插入到id已经不对了,应该每次重新构建一个新的ContentProviderOperation列表。
所以正确的处理方式是:每次截取联系人列表的一段,构建一个ContentProviderOperation列表,插入完成以后,取联系人列表的下一段,再重新构建一个新的ContentProviderOperation列表,再次插入
所以正确的代码是这样的

private void syncTSContactsToContactsProvider(List<TsContact> contactsList) {final int contactsListSize = contactsList.size();int unitLength = 400; //large insert will cause binder data overflow.int syncedCount = 0;while (syncedCount < contactsListSize) {int syncLength = (contactsListSize - syncedCount) < unitLength ? (contactsListSize - syncedCount) : unitLength;ArrayList<ContentProviderOperation> ops = new ArrayList<>();for (int index = 0; index < contactsList.size(); index++) {TsContact contact = contactsList.get(index);int rawContactInsertIndex = ops.size();ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null).withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.mLastName).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.mFirstName).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.mMiddlename).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contact.mPhoneNumber).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, contact.mPhoneType).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "").withYieldAllowed(true).build());}try {getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);ops.clear();} catch (final TransactionTooLargeException e) {e.printStackTrace();} catch (final RemoteException e) {LogUtil.e(TAG, "RemoteException in commit");e.printStackTrace();} catch (final OperationApplicationException e) {LogUtil.e(TAG, "OperationApplicationException in commit");e.printStackTrace();}Log.d(TAG, "" + syncedCount + "contacts has been synced to contacts provider" );}
}

Android中向ContactsProvider中插入大量联系人相关推荐

  1. Android中获取手机中的联系人信息

    #Android中获取手机中的联系人信息 1.0.查看系统通讯录的表,表路径:data->data->com.android.providers.contacts->database ...

  2. 【Android】Fragment官方中文文档

    以下内容来自Android官方文档. Fragment 表示 Activity 中的行为或用户界面部分.您可以将多个片段组合在一个 Activity 中来构建多窗格 UI,以及在多个 Activity ...

  3. android studio数据库存储数据,如何使用API​​ 23在android studio中的数据库中存储数据?...

    大多数时候我不会发布任何内容,因为我可以在其他帖子中找到我需要的所有内容,但是现在我已经有几天了,您如何在数据库中存储任何内容?这是我的Java代码如何使用API​​ 23在android studi ...

  4. Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展

    本文由"猫爸iYao"原创分享,感谢作者. 1.引言 最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样: ▲ 微信群聊界面里的@人功能  ▲ Q ...

  5. 关于android隐式启动activity的分析和说明,Android学习之Intent中显示意图和隐式意图的用法实例分析...

    本文实例讲述了Android学习之Intent中显示意图和隐式意图的用法.分享给大家供大家参考,具体如下: Intent(意图)主要是解决Android应用的各项组件之间的通讯. Intent负责对应 ...

  6. Android中的USB中的UsbAccessory和UsbDevice的区别

    Android中的USB中的UsbAccessory和UsbDevice的区别 [背景] 之前折腾android中的USB相关的东西. 遇到两个东西: UsbAccessory和UsbDevice 但 ...

  7. Android那些事儿 成长中的Android(1)

    Android 是近年来在移动设备行业相当火热的词汇,在此我从交互设计的角度整理了此文章,文章分为三个部分,跟大家讨论Android的那些事儿. 第一部分:成长中的Android; ◆什么是Andro ...

  8. Android中的权限-中英对照

    <uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES" ></u ...

  9. Android5.1中Contacts模块拨号加载联系人信息流程

    基于5.1代码Contacts模块拨号流程 之前的总结介绍过联系人界面的快速拨号流程以及显示界面的接收数据过程,现在着重讲中间是怎么从OutgoingBroadcast过来拨号界面,中间的联系人信息是 ...

  10. android socket握手,HttpURLConnection抛出java.net.SocketTimeoutException:在Android 4.1.1中SSL握手超时...

    在Android 5.0及更高版本中运行时,我的代码运行正常.但是在Android 4.1.1中它会抛出java.net.SocketTimeoutException:SSL握手超时. URL url ...

最新文章

  1. session一致性架构设计极简教程
  2. win10恢复经典开始菜单_小编教你电脑如何升级win10
  3. C++类构造函数中的成员初始化
  4. linux命令ifconfig
  5. visual studio 调试时提示 已加载“C:\Windows\SysWOW64\ntdll.dll”。无法查找或打开 PDB 文件。
  6. Centos 7下安装nginx,使用yum install nginx,提示没有可用的软件包(亲测)
  7. mysql 5.0.37.tar.gz_Linux下MySQL5.0.37安装配置步骤
  8. 沈抚示范区·“华为云杯”2021全国AI大赛圆满落幕
  9. 2、根据@FeignClient生成代理类
  10. 2019年计算机学业水平测试填空题,2019年计算机学业水平模拟测试选择题80题Word(含参考答案)...
  11. Android 四大组件学习之BroadcastReceiver一
  12. COOLFluiD安装教程
  13. html转换txt文件,HTML网页转TXT文件、文本转换器_TxtEasy! V1.5.5 免费版
  14. MySQL 幻读被彻底解决了吗?
  15. 华为交换机密码遗失怎么办?华为交换机密码恢复方法
  16. 脑机接口专栏 | 如何分析静息状态的fMRI数据?(二)
  17. 【BZOJ 1050】旅行comf
  18. 日本东京市区坐车到成田机场
  19. Reality Labs首次向媒体开放,空间音频、EMG腕带体验大公开
  20. 导出期刊对应格式的参考_EndNote 输出样式模板(根据国家标准制订)

热门文章

  1. AdBlock插件离线安装
  2. Android USBCamera投屏 - 利用UVC协议将手机上的画面有线投屏到Android车机的屏幕上
  3. python中文版下载-python3.8.1汉化版
  4. 经济管理学中常用的模型分析法
  5. 基于java网上商城源码设计
  6. 工具DebugView、PCHunter、Procexp、Procmon
  7. python写电影推荐系统_Netflix电影推荐系统Python实现(协同过滤+矩阵分解)
  8. nginx+Keeplive高可用集群部署
  9. 工业铝型材是怎样去生产的
  10. linux卸载xmind,Ubuntu下安装Xmind