Android developers are certainly familiar with using ContentProvider to insert data, and we generally go through the following two steps when using ContentProvider:

  • Declare the definition of ContentProvider and its related URI, write the corresponding Provider to add, delete, change and check methods;
  • Use the ContentResolver and its corresponding URI to add, delete, change, and check the ContentProvider.

For using ContentProvider to insert data, you can use two API interfaces, insert and bulkInsert respectively. The former is used for single data insert operation, and the latter is more suitable for batch data insert operation. After a brief understanding of the relevant knowledge of ContentProvider, Take a look at the following code:

public static void addOrUpdateContacts(Context context, Collection 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();
    }
}Copy the code

The incoming contacts is a set of contact information data, through a loop into a ContentValues types of arrays, then use the context, getContentResolver () will bulkInsert ContentValues array is inserted into the database. After a brief look at what this code does, do you think there is a problem with this way of writing?

If contacts has a large enough number of elements (let’s say 10,000 elements, each of which has about 200 bytes), then the converted ContentValues array is quite large. At this time, bulkInsert is used to insert data, and the return of successfully inserted data is 0, which means that our insert operation does not take effect. The reason it doesn’t work is that the data inserted at one time is too large, because The Underlying data communication is with Binder, which is mentioned in the documentation for Binder

The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.

So, when the bulk insert data we need to pay attention to deal with the limit to the size of the problem, if the data volume is too big, can lead to TransactionTooLargeException Binder. The simple improved code looks like this:

public static void addOrUpdateContacts(Context context, Collection contacts) { if (contacts == null || contacts.isEmpty()) { return; } List 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) {// If (buffer.size() == 1024) {// If (buffer.size() == 1024) {// If (buffer.size() == 1024) {// If (buffer.size() == 1024) {// If (buffer.size() == 1024) {// If (buffer.size() == 1024) { 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(); }}}Copy the code

The idea was to break up large amounts of data and insert them in batches to avoid exceeding the limitations of the Binder’s data buffer. In practice, this problem is often overlooked for the following reasons:

  • The user base of the App is not large enough to trigger this problem;
  • See only the ContentProvider inserts data, without thinking about the limitations of the underlying Binder buffers;
  • Even more than Binder buffer limit, also won’t quote TransactionTooLargeException bulkInsert;

Internal did not quote TransactionTooLargeException because ContentResolver RemoteException capture digestion, and directly return 0, and not the log output

It doesn’t feel like a good design, and it hides potential problems even deeper. This article is a recent record of dealing with an online issue in your app. If your app doesn’t address the issues described above, consider doing so.

This article is the original work of TechVision. Please indicate the source of the original work.Blog.coderclock.com/2017/04/29/…, welcome to follow my wechat official account:Technology horizon