为什么ContentResolver调用bulkInsert批量插入数据失败
来源:互联网 发布:初中数学上课软件 编辑:程序博客网 时间:2024/05/16 02:40
做Android开发的朋友肯定对使用ContentProvider插入数据并不陌生,通常我们使用ContentProvider基本都是经历如下两个步骤:
- 声明定义ContentProvider及其相关的URI,编写Provider中对应的增删改查方法;
- 使用ContentResolver及其对应的URI来对ContentProvider进行增删改查操作;
对于使用ContentProvider进行插入操作,分别可以使用insert、bulkInsert两个API接口,前者用于单条数据插入操作,后者则更适合批量数据插入操作,简单的了解了一遍ContentProvider的相关知识后,来看看下面这段代码:
public static void addOrUpdateContacts(Context context, Collection<ContactStruct> contacts) { if (contacts == null || contacts.isEmpty()) { return; } final int kCount = contacts.size(); ContentValues[] valuesArray = new ContentValues[kCount]; int pos = 0; for (ContactStruct contact : contacts) { ContentValues values = new ContentValues(); values.put(ContactTable.COLUMN_UID, contact.uid); values.put(ContactTable.COLUMN_NAME, contact.name); values.put(ContactTable.COLUMN_PHONE, contact.phone); values.put(ContactTable.COLUMN_PINYIN, contact.pinyin); values.put(ContactTable.COLUMN_REMARK, contact.remark); // add "REPLACE" flag values.put(ContactProvider.SQL_INSERT_OR_REPLACE, true); valuesArray[pos++] = values; } try { int ret = context.getContentResolver().bulkInsert(ContactProvider.Contact.CONTENT_URI, valuesArray); if (ret != kCount) { Log.e(Log.TAG_DATABASE, "addOrUpdateContacts partial failed, succ:" + ret + ",total:" + kCount); } } catch (Exception e) { e.printStackTrace(); } }
其传入contacts是一组联系人信息数据,通过一个循环的转换成ContentValues类型的数组,再使用context.getContentResolver().bulkInsert将ContentValues数组插入到数据库中去。简单的了解这段代码的作用后,大家觉得这种写法是否存在问题呢?
这里不卖关子,这段代码逻辑上并没有问题的,但实现上忘了考虑contacts的大小问题,如果contacts的元素数量足够多(假设有10000个元素,每个元素有200个Byte左右),则转换后的ContentValues数组也是相当大的,这时候再使用bulkInsert去插入数据,返回成功插入的数据为0,意味着我们的插入操作并没有生效。没有生效的原因在于一次性插入的数据过大,由于ContentProvider底层数据通信是采用了Binder,而关于Binder的文档也提到了
The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.
所以,在批量插入数据的时候我们需要注意处理这个大小限制的问题,如果数据量过大,会导致Binder报TransactionTooLargeException。简单改良后的代码如下:
public static void addOrUpdateContacts(Context context, Collection<ContactStruct> contacts) { if (contacts == null || contacts.isEmpty()) { return; } List<ContentValues> buffer = new LinkedList<>(); for (ContactStruct contact : contacts) { ContentValues values = new ContentValues(); values.put(ContactTable.COLUMN_UID, contact.uid); values.put(ContactTable.COLUMN_NAME, contact.name); values.put(ContactTable.COLUMN_PHONE, contact.phone); values.put(ContactTable.COLUMN_PINYIN, contact.pinyin); values.put(ContactTable.COLUMN_REMARK, contact.remark); values.put(ContactProvider.SQL_INSERT_OR_REPLACE, true);// add "REPLACE" flag buffer.add(values); if (buffer.size() == 1024) { //Buffer每满1024条数据,就往数据库批量插入一次,不要一次性插入非常大的数组,底层的Binder有数据限制,会报Exception导致插入失败 ContentValues[] bufferDataArray = new ContentValues[buffer.size()]; buffer.toArray(bufferDataArray); int successRow = context.getContentResolver().bulkInsert(ContactProvider.Contact.CONTENT_URI, bufferDataArray); if (successRow != bufferDataArray.length) { Log.e(Log.TAG_DATABASE, "addOrUpdateContacts buffer data failed, success row:" + successRow); } else { Log.i(Log.TAG_DATABASE, "addOrUpdateContacts success!"); buffer.clear(); Log.i(Log.TAG_DATABASE, "addOrUpdateContacts clear buffer"); } } } if (buffer.size() > 0){//缓冲区还有数据 ContentValues[] bufferDataArray = new ContentValues[buffer.size()]; buffer.toArray(bufferDataArray); int successRow = context.getContentResolver().bulkInsert(ContactProvider.Contact.CONTENT_URI, bufferDataArray); if (successRow != bufferDataArray.length) { Log.e(Log.TAG_DATABASE, "addOrUpdateContacts flush buffer data failed, success row:" + successRow); } else { Log.i(Log.TAG_DATABASE, "addOrUpdateContacts flush success!"); buffer.clear(); } } }
思路是将大量的数据进行拆分,分批批量插入,避免超过Binder传递数据缓冲区的限制问题。实际开发中,这个问题比较容易被忽略,主要原因有以下几个:
- App的用户群体还不足够庞大,不能触发此问题;
- 只看到ContentProvider插入数据,没有联想到底层Binder缓冲区的限制;
- 即使超过Binder缓冲区的限制,bulkInsert也不会报TransactionTooLargeException;
之所以不会报TransactionTooLargeException是因为ContentResolver内部做了RemoteException的捕获消化,并直接return 0,且并没有log输出
感觉并不是一个很好的设计,反而把潜在的问题藏得更深了,本文便是最近处理应用某个线上问题的一点记录,如果你的应用也没有考虑以上描述的问题,不妨可以考虑处理一下。
本文为技术视界原创作品,转载请注明原文出处:http://blog.coderclock.com/2017/04/29/android/why_use_contentresolver_bulkinsert_return_0 ,欢迎关注我的微信公众号:技术视界
- 为什么ContentResolver调用bulkInsert批量插入数据失败
- EF大数据批量处理----BulkInsert
- EntityFramework.BulkInsert扩展插入数据和EF本身插入数据比较
- EntityFramework.BulkInsert扩展插入数据和EF本身插入数据比较
- BulkInsert方法实现批量导入
- 批量保存,批量插入数据
- mongodb批量插入插入数据
- 批量插入数据重复插入
- 批量插入数据集
- asp批量插入数据
- 批量插入数据
- 批量插入数据
- Oracle批量插入数据
- mybatis数据批量插入
- mysql批量插入数据
- hibernate批量插入数据
- 批量插入数据
- 批量插入数据方法
- dojo中的dojo/dom-style
- 关联查询_association分步查询,简化查询语句
- Discuz!论坛教程之注册成功后跳转到指定页面
- MaterialDesign
- 安卓7.0文件访问严苛模式(如安卓APK报错等原因)
- 为什么ContentResolver调用bulkInsert批量插入数据失败
- CString与char *互转总结
- Mybatis 一对一,一对多,多对一,多对多的理解
- linux下网络编程
- chrome浏览器初始页面黑屏
- 剑指Offer——(21)栈的压入、弹出序列
- AT91sam9260ek开发板linux编译
- jdk7中hashmap实现原理和jdk8中hashmap的改进方法总结
- Storm拓扑