Life Before Loaders (part 1)
This post gives a brief introduction to Loader
s and the LoaderManager
. The first section describes how data was loaded prior to the release of Android 3.0, pointing out out some of the flaws of the pre-Honeycomb APIs. The second section defines the purpose of each class and summarizes their powerful ability in asynchronously loading data.
This is the first of a series of posts I will be writing on Loaders and the LoaderManager:
- Part 1: Life Before Loaders
- Part 2: Understanding the LoaderManager
- Part 3: Implementing Loaders
- Part 4: Tutorial: AppListLoader
If you know nothing about Loader
s and the LoaderManager
, I strongly recommend you read the documentation before continuing forward.
The Not-So-Distant Past
Before Android 3.0, many Android applications lacked in responsiveness. UI interactions glitched, transitions between activities lagged, and ANR (Application Not Responding) dialogs rendered apps totally useless. This lack of responsiveness stemmed mostly from the fact that developers were performing queries on the UI thread—a very poor choice for lengthy operations like loading data.
While the documentation has always stressed the importance of instant feedback, the pre-Honeycomb APIs simply did not encourage this behavior. Before Loaders, cursors were primarily managed and queried for with two (now deprecated) Activity
methods:
-
public void startManagingCursor(Cursor)
Tells the activity to take care of managing the cursor's lifecycle based on the activity's lifecycle. The cursor will automatically be deactivated (
deactivate()
) when the activity is stopped, and will automatically be closed (close()
) when the activity is destroyed. When the activity is stopped and then later restarted, the Cursor is re-queried (requery()
) for the most up-to-date data. -
public Cursor managedQuery(Uri, String, String, String, String)
A wrapper around the
ContentResolver
'squery()
method. In addition to performing the query, it begins management of the cursor (that is,startManagingCursor(cursor)
is called before it is returned).
While convenient, these methods were deeply flawed in that they performed queries on the UI thread. What's more, the "managed cursors" did not retain their data across Activity
configuration changes. The need to requery()
the cursor's data in these situations was unnecessary, inefficient, and made orientation changes clunky and sluggish as a result.
The Problem with "Managed Cursor
s"
Let's illustrate the problem with "managed cursors" through a simple code sample. Given below is a ListActivity
that loads data using the pre-Honeycomb APIs. The activity makes a query to the ContentProvider
and begins management of the returned cursor. The results are then bound to aSimpleCursorAdapter
, and are displayed on the screen in a ListView
. The code has been condensed for simplicity.
|
|
There are three problems with the code above. If you have understood this post so far, the first two shouldn't be difficult to spot:
-
managedQuery
performs a query on the main UI thread. This leads to unresponsive apps and should no longer be used. -
As seen in the
Activity.java
source code, the call tomanagedQuery
begins management of the returned cursor with a call tostartManagingCursor(cursor)
. Having the activity manage the cursor seems convenient at first, as we no longer need to worry about deactivating/closing the cursor ourselves. However, this signals the activity to callrequery()
on the cursor each time the activity returns from a stopped state, and therefore puts the UI thread at risk. This cost significantly outweighs the convenience of having the activity deactivate/close the cursor for us. -
The
SimpleCursorAdapter
constructor (line 32) is deprecated and should not be used. The problem with this constructor is that it will have theSimpleCursorAdapter
auto-requery its data when changes are made. More specifically, the CursorAdapter will register a ContentObserver that monitors the underlying data source for changes, callingrequery()
on its bound cursor each time the data is modified. The standard constructor should be used instead (if you intend on loading the adapter's data with aCursorLoader
, make sure you pass0
as the last argument). Don't worry if you couldn't spot this one... it's a very subtle bug.
With the first Android tablet about to be released, something had to be done to encourage UI-friendly development. The larger, 7-10" Honeycomb tablets called for more complicated, interactive, multi-paned layouts. Further, the introduction of the Fragment
meant that applications were about to become more dynamic and event-driven. A simple, single-threaded approach to loading data could no longer be encouraged. Thus, the Loader
and theLoaderManager
were born.
Android 3.0, Loaders, and the LoaderManager
Prior to Honeycomb, it was difficult to manage cursors, synchronize correctly with the UI thread, and ensure all queries occurred on a background thread. Android 3.0 introduced the Loader
and LoaderManager
classes to help simplify the process. Both classes are available for use in the Android Support Library, which supports all Android platforms back to Android 1.6.
The new Loader
API is a huge step forward, and significantly improves the user experience. Loader
s ensure that all cursor operations are done asynchronously, thus eliminating the possibility of blocking the UI thread. Further, when managed by the LoaderManager
, Loader
s retain their existing cursor data across the activity instance (for example, when it is restarted due to a configuration change), thus saving the cursor from unnecessary, potentially expensive re-queries. As an added bonus, Loader
s are intelligent enough to monitor the underlying data source for updates, re-querying automatically when the data is changed.
Conclusion
Since the introduction of Loader
s in Honeycomb and Compatibility Library, Android applications have changed for the better. Making use of the now deprecated startManagingCursor
and managedQuery
methods are extremely discouraged; not only do they slow down your app, but they can potentially bring it to a screeching halt. Loader
s, on the other hand, significantly speed up the user experience by offloading the work to a separate background thread.
In the next post (titled Understanding the LoaderManager), we will go more in-depth on how to fix these problems by completing the transition from "managed cursors" to making use of Loader
s and the LoaderManager
.
Don't forget to +1 this blog in the top right corner if you found this helpful!