If you fetch the data from any
ContentProvider in your application, the most likely scenarios are:
- You’re absolutely clueless and fetch the data on UI thread.
- You’re using an
- Your app crashes on screen orientation change.
- You block the screen orientation, because you googled this “solution” on StackOverflow.
- You wrote error prone boilerplate code to detach and reattach the
AsyncTaskfrom the Activity.
- You are using
ContentProvider is probably not the only type of asynchronous operations you perform. You might want to access
SharedPreferences, read a file or query a web API. In that case you need
Loader<SomeOtherDataThanCursor>, but implementing one correctly is a bit tricky.
I’ll walk you through the entire process of understanding how the Loaders work, implementing a sane base class for your Loaders, implementing
CursorLoader with all issues fixed and extending it to allow multiple notification Uris. It’ll be a long post so grab a cup of your favourite beverage.
Loader should do three things:
- Load data in background thread.
- Cache the loaded data, so you won’t reload it after screen orientation change.
- If applicable, monitor the data source and reload the data when necessary.
The Loader class itself doesn’t provide any mechanism for loading the data in background thread. You either have to implement this yourself, or you can subclass the
AsyncTaskLoader. This covers the first point on our requirements list.
The 2nd point is not handled by
AsyncTaskLoader. In fact the
AsyncTaskLoader is far from being fully functional, for example this perfectly reasonable looking implementation won’t work:
1 2 3 4 5 6 7 8 9 10
A good starting point for creating either
CursorLoader implementation or LoaderCustom.java from SDK samples. Here’s the common part of these two implementations, which provides all necessary boilerplate for loading and caching the data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
Why this class is not provided by the framework is a mystery to me, but hey, that’s just one more thing you have to know when coding for Android platform. Now you can write your custom Loader like this:
1 2 3 4 5 6 7 8 9 10
But why do we need all that code? The key to understanding the Loaders is understanding the expected Loader’s behaviour in different states: started, stopped, abandoned and reset. Upon entering each state, the appropriate callback is executed:
onStartLoading: the Loader was created and should either load the data or return cached data.
onStopLoading: the Loader should keep the cached data and monitor the data source for changes, but it shouldn’t load the data. This happens for example when users presses home button from your app.
onAbandoned: someone restarted the Loader. New instance of this Loader was created in
onCreateLoadercallback in your
Activity/whatever and loads new data. The abandoned Loader should keep the data until the new Loader loads and delivers it’s data. There is no point of monitoring data source or reloading the data in abandoned Loader – the data will be loaded by the new instance. When new Loader delivers it’s data this Loader will be reset.
onReset: the data previously loaded by this Loader are no longer used and should be cleaned up. This Loader might be started again, so make sure you clean up also any old state in your Loader implementation.
AsyncTaskLoader provides additional callback:
onCancelled: called after data loading when it turns out that this data is no longer needed, for example when the
onLoadInBackgroundwas cancelled. In this callback you should take care of releasing resources.
Since the releasing resources should be also performed in
onReset callback and in our deliverResults implementation, our AbstractLoader class provides handy
releaseResources() callback for closing your
Cursors, file handles, etc.
Now let’s walk through our
AbstractLoader implementation. When someone starts our Loader using
onStartLoading is called:
1 2 3 4 5 6 7 8 9 10 11 12 13
We keep the loaded data in
mResult member of our
AbstractLoader. If we have already loaded the data, we can just deliver the results to Loader clients. If the cache is empty or the Loader was notified about new data available for fetching, we force data reload by calling
forceLoad() method. It starts
AsyncTask which executes
loadInBackground in background thread and the result is passed to
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
A lot of interesting things happen here. First, we check if the loader was put into
reset state. In this state all the previous resources were already released, so we just need to take care of newly loaded data. Then we swap the data in cache, call
deliverResults in Loader and then release the resources for previously cached data.
When the Fragment or Activity with active Loader is stopped, the Loaders are also put in the stopped state. It means that they should keep the cached data, monitor if this data is still valid, but they should not actively load the data or deliver the results to UI thread. In terms of
AsyncTaskLoader it means that any running
AsyncTasks should be cancelled:
1 2 3 4
Current implementation of
AsyncTaskLoader do not interrupt the active tasks, it only marks that the results of these tasks should not be delivered to the UI thread. However, the results might require some resource releasing, so the
onCancelled callback is called:
1 2 3 4 5
The last callback we have to implement is
1 2 3 4 5 6 7 8 9 10
There are two important things here. First, the Loader can be moved to reset state from started state, which means it can still have active
loadInBackground. We need to stop them first. Then, as per the specified contract, we have to release the resources and clear the cache.
onAbandoned callback? AbstractLoader doesn’t monitor any data source by itself, so this callback doesn’t have to be implemented.
So how would we implement observing a data source and automatic reloading? Let’s see how would the
CursorLoader implementation look like had we used our AbstractLoader as a base class (literally; if you merge MyCursorLoader and AbstractLoader code samples from this post, you’ll get exactly the CursorLoader implementation from support-v4):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
This implementation has two bugs and one kind-of-feature. Let’s start with the last one.
onStartLoading contract specifies, that you should start monitoring the data source upon entering this state. But let’s think what would happen if you had a query that takes 200ms to run and your data source would change every 150ms. The loader would never deliver any data, because every load request would be cancelled in middle of
loadInBackground execution by our content observer.
I guess that’s why the Android implementation of
CursorLoader registers the observer after the data is loaded. This way the first results are delivered as soon as possible, but for subsequent loads the data is delivered only when data didn’t change during loading. I’m not sure if it was intentional, or this behavior was implemented by accident, but it makes sense to me, so let’s adjust our Loaders contract and implement this behavior in our AbstractLoader.
But if you look closely, the
CursorLoader implementation violates even this updated contract. Remember that
deliverResults are executed on separate threads. So what would happen if the data observer is triggered after
registerContentObserver call, but before the
deliverResults? We’d get exactly the same behavior we’d get had we registered the ContentObserver in
onStartLoading – the loader would never deliver it’s data. That’s the first bug.
The second issue with
CursorLoader implementation is violation of
onAbandon callback contract. If someone calls restartLoader and the content observer is triggered, the abandoned Loader instance will happily reload it’s data just to throw it away.
You can dismiss it as something that would happen only 1% of the time and has negligible impact, and if we were talking about application code, I’d agree with you, but IMO library code that will be used by thousands of developers should be held to a higher standard.
Here’s the wrap up of changes in behavior:
ContentObserver after the first data is delivered, not after the first data is loaded.
The first point requires changes to
deliverResult method, so it makes sense to modify our AbstractLoader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
CursorLoader implementation would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
The second part – unregistering observer in
onAbandon – is tricky. It’s illegal to call
Cursor.unregisterContentObserver with observer that wasn’t registered and the
onAbandon can be called when the
deliverResults wasn’t called (see
AsyncTaskLoader.dispatchOnLoadComplete() implementation). One solution would be keeping the set of Cursors that were registered, but it’s not optimal. Instead, we can create a proxy ContentObserver that can be enabled or disabled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
CursorLoader is a bit special case, because the
Cursor itself contains
ContentObservable. In most cases however the content observers and loaded data would be completely separated. For these cases it would be useful to have a base class for Loader which registers some
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
We need to keep the registered state in our Loader, because the default
Observable implementation doesn’t like registering the same observer twice or unregistering not registered observer.
Now we can use this class as a base for a Loader which should be reloaded when one of specified
Uris is triggered:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
I think the
Loaders aren’t as bad as some people think and say. Four Loader states might seem complicated at first, but if you think about Android
Activity lifecycle they make perfect sense and they are something you’d have to implement yourself, with a high probability of mucking things up. The only thing lacking is documentation and sane base classes for extensions, something I hope I delivered through this blog post.
+CommonsWare wrote few weeks ago that he considers Loader to be a failed abstraction, mostly because the interface assumes there is a single object which notifies the Loader about new data. He concluded his post with the following sentence:
In my case, if I am going to have some singleton manager object, with distinct data objects per operation, I am going to use something more flexible than Loader, such as an event bus.
AbstractObservingLoader to load some data from
SQLiteDatabase and subscribe to some event bus for model change events should be trivial, and you’d get a lot of things for free – performing loads in background, cancelling loads, caching results, invalidating cache, and so on.
Having said that,
Loaders are not solution to every single problem. They are coupled with activity lifecycle, so they are not suitable for long running tasks that should not be interrupted when user navigates away. In these cases the
IntentService, or some other
Service implementation is a better choice.