Android中向ContactsProvider中插入大量联系人(编辑中)
来源:互联网 发布:海关数据免费查询系统 编辑:程序博客网 时间:2024/05/29 16:54
应用场景
项目中存在这样的需求,通过设备之间通过蓝牙传输联系人,并且需要将获取过来的联系人插入到ContactsProvider中
批量插入联系人的标准代码
在Android的源码中,ContactsContract.java中为我们展示了批量插入联系人的方法。
The batch method is by far preferred. It inserts the raw contact and its constituent data rows in a single database transaction and 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, "Mike Sullivan") .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中插入大量联系人(编辑中)
- 向Android手机中插入一条短信 及联系人获取
- 向oracle表中插入大量数据
- 向db2中插入大量数据处理方法
- 如何向mysql中插入大量数据
- Oracle中插入大量测试数据
- 快速向表中插入大量数据Oracle中append与Nologging
- 快速向表中插入大量数据Oracle中append与Nologging
- 快速向表中插入大量数据Oracle中append与Nologging
- 快速向表中插入大量数据Oracle中append与Nologging
- 通过adb命令向Android模拟器中导入通讯录联系人
- 通过adb命令向Android模拟器中导入通讯录联系人
- 通过adb命令向Android模拟器中导入通讯录联系人
- 如何向android手机通讯录中添加联系人
- Android中联系人导入
- Android中联系人使用
- android中搜索联系人
- Android中读取联系人
- Java基础之封装
- springboot上传文件到阿里云OSS
- JeeSite框架学习------主子表创建总结
- 【二分图最大匹配】BZOJ4554 [Tjoi2016&Heoi2016]游戏
- Java中Runnable和Thread的区别
- Android中向ContactsProvider中插入大量联系人(编辑中)
- C++表达式的执行原理
- JS(去掉前后空格或去掉所有空格)的用法
- 7.26 机房欢乐赛 T1 无尽的矩阵 (hash + KMP)
- 视频直播的一些概念性的东西 码率 sdn 等
- java实现图片裁剪、缩放功能
- Ubuntu一个直接以窗口的形式打开文件夹的命令
- 虚拟机安装tomcat
- 收纳的JS正则表达式