为什么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 ,欢迎关注我的微信公众号:技术视界

0 0
原创粉丝点击