Android: Better Performance with ContentProviderOperation

ContentProviders are one of Android’s core building blocks. They represent a relational interface to data – either in databases or (cached) data from the cloud.

Sometimes you want to use them for multiple operations in a row. Like updating different sources and so on. In those cases you could call the respective ContentResolver methods multiple times or you could execute a batch of operations. The latter is the recommended practise.

To create, delete or update a set of data in a batch like fashion you should use the class ContentProviderOperation.

According to Android’s documentation it is recommended to use ContentProviderOperations for multiple reasons:

  • All operations execute within the same transaction – thus data integrity is assured
  • This helps improve performance since starting, running and closing one transaction offers far better performance than opening and committing multiple transactions
  • Finally using one batch operation instead of multiple isolated operations reduces the number of context switches between your app and the content provider you are using. This of course also helps to improve the performance of your app – and by using less cpu cycles also reduces the power consumption.

To create an object of ContentProviderOperation you need to build it using the inner class ContentProviderOperation.Builder. You obtain an object of the Builder class by calling one of the three static methods newInsert, newUpdate or newDelete:

Methods to obtain a Builder object
Method Usage
newInsert Create a Builder object suitable for an insert operation
newUpdate Create a Builder object suitable for an update operation
newDelete Create a Builder object suitable for a delete operation

The Builder is an example of the Gang of Four Builder pattern. A Builder defines an interface for how to create objects. Concrete instances then create specific objects for the task at hand. In this case we have three different Builders for creating ContentProviderOperation objects. These objects can be used to create, update or delete ContentProvider data sets.

Typically all steps necessary to create a ContentProviderOperation object are done in one round of method chaining. That’s possible because all methods of the Builder class return a Builder object themself. The one exception is the build() method, which instead returns the desired object: Our completely created ContentProviderOperation object. So a typical chain might look like this:

ArrayList<ContentProviderOperation> ops = 
   new ArrayList<ContentProviderOperation>();
ops.add(
   ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
       .withValue(RawContacts.ACCOUNT_TYPE, "someAccountType")
       .withValue(RawContacts.ACCOUNT_NAME, "someAccountName")
       .withYieldAllowed(true)
       .build());

Of course you could also use a ContentValues object as usual and use the withValues(values) method instead.

The Builder class has among others these methods you can use to define which objects to delete or how to create or update an object:

Some important methods of the Builder object
Method Usage
withSelection (String selection, String[] selectionArgs) Specifies on which subset of the existing data set to operate. Only usable with ContentProviderOperation objects used to update or delete data
withValue (String key, Object value) Defines the desired value for one column. Only usable with ContentProviderOperation objects used to create or update data
withValues (ContentValues values) Defines the desired values for multiple columns. Only usable with ContentProviderOperation objects used to create or update data

As you can see in the code sample I presented above you need an ArrayList of ContentProviderOperation objects. For every ContentProvider-CRUD method you have to use one ContentProviderOperation object and add it to this list. I will explain in a later blog post about the method withValueBackReference() why it has to be an ArrayList and not say a LinkedList.

The list is finally passed to the applyBatch() method of the ContentResolver object:

try {
   getContentResolver().
      applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
   // do s.th.
} catch (OperationApplicationException e) {
   // do s.th.
}

That’s all for now. I will explain two methods of the Builder class, that are not well documented, in separate follow up posts.

Share this article:

You can leave a response, or trackback from your own site.

5 Responses to “Android: Better Performance with ContentProviderOperation”

  1. Ataul Munim says:

    Hi, thanks for this series – I felt your posts on Loaders helped explain the concept in a straightforward fashion, so I’m returning for more :)

    I have a few questions on usage of Content Providers:
    - Do the CRUD operations in ContentResolver run on a background thread or is it recommended to use ASyncTask when inserting/updating/deleting rows (not many) when using these methods?

    - Is there any overhead to using Content Provider Operations if there is only one ContentProvider-CRUD method you need to run? Is it suggested to *always* use Content Provider Operations, or *only* when it might appear you need batch operations, like when syncing remotely?

    Thanks!
    Ataul

    • No, ContentProvider methods run on the UI thread. That’s why you should use Loaders.

      If you want to delete or insert stuff, you could simply start a normal thread. Unless your UI needs to be informed after the deletion has happened. In that case you would need an AsyncTask.

      In general: Use AsyncTasks when you need to manipulate the UI when the thread has finished working.

      There is no need to use ContentProviderOperations if only one query or modification statement is used. I only use them for batch operations or when update/delete/insert statements depend on the result of a previous query (that is they depend on the ID returned by the previous statement).

  2. brescia123 says:

    Hi, nice post! There’s a way to have information about the progress of the batch operation? thanks!

  3. Yuku says:

    Thanks for the useful article! I wonder why the list of ContentProviderOperations has to be an ArrayList and not say a LinkedList when passed to applyBatch. Do you have any idea why?

    As far as I can trace from the source code, applyBatch is eventually passed to ContentProviderNative, and it contains this code:

    public ContentProviderResult[] applyBatch(ArrayList operations)
    throws RemoteException, OperationApplicationException {
    ……
    data.writeInt(operations.size());
    for (ContentProviderOperation operation : operations) {
    operation.writeToParcel(data, 0);
    }

    so it seems that just a List works, no need to be an ArrayList.

    • No idea. But probably just a thoughtless deed. The quality of the source differs very much from class to class – some excellent, some not so. I also see no reason for choosing an ArrayList instead of letting the clients choose the type of list.

      They actually use it, though, in ContentProvider. Here they use this construct in applyBatch():

      final int numOperations = operations.size();
      final ContentProviderResult[] results = new ContentProviderResult[numOperations];
      for (int i = 0; i < numOperations; i++) {
         results[i] = operations.get(i).apply(this, results, i);
      }
      

      Though, why they don’t use the Java-variant of a for-each loop here is beyond me. With that the type of List would have been irrelevant.

Leave a Reply

You can also subscribe without commenting.