a brief introduction to Loaders and the LoaderManager

Life Before Loaders (part 1)

Posted Jul 6, 2012

by Alex Lockwood


This post gives a brief introduction to Loaders 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:

If you know nothing about Loaders 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's query() 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 Cursors"

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.

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

public class SampleListActivity extends ListActivity {

 private static final String[] PROJECTION = new String[] {"_id", "text_column"};

 @Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   // Performs a "managed query" to the ContentProvider. The Activity
   // will handle closing and requerying the cursor.
   //
   // WARNING!! This query (and any subsequent re-queries) will be
   // performed on the UI Thread!!
   Cursor cursor = managedQuery(
       CONTENT_URI,  // The Uri constant in your ContentProvider class
       PROJECTION,   // The columns to return for each data row
       null,         // No where clause
       null,         // No where clause
       null);        // No sort order

   String[] dataColumns = { "text_column" };
   int[] viewIDs = { R.id.text_view };

   // Create the backing adapter for the ListView.
   //
   // WARNING!! While not readily obvious, using this constructor will
   // tell the CursorAdapter to register a ContentObserver that will
   // monitor the underlying data source. As part of the monitoring
   // process, the ContentObserver will call requery() on the cursor
   // each time the data is updated. Since Cursor#requery() is performed
   // on the UI thread, this constructor should be avoided at all costs!
   SimpleCursorAdapter adapter = new SimpleCursorAdapter(
       this,                // The Activity context
       R.layout.list_item,  // Points to the XML for a list item
       cursor,              // Cursor that contains the data to display
       dataColumns,         // Bind the data in column "text_column"...
       viewIDs);            // ...to the TextView with id "R.id.text_view"

   // Sets the ListView's adapter to be the cursor adapter that was
   // just created.
   setListAdapter(adapter);
 }
}

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:

  1. managedQuery performs a query on the main UI thread. This leads to unresponsive apps and should no longer be used.

  2. As seen in the Activity.java source code, the call to managedQuerybegins 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 call requery() 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.

  3. The SimpleCursorAdapter constructor (line 32) is deprecated and should not be used. The problem with this constructor is that it will have the SimpleCursorAdapter auto-requery its data when changes are made. More specifically, the CursorAdapter will register a ContentObserver that monitors the underlying data source for changes, calling requery() 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 a CursorLoader, make sure you pass 0as 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. Loaders ensure that all cursor operations are done asynchronously, thus eliminating the possibility of blocking the UI thread. Further, when managed by the LoaderManager, Loaders 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, Loaders are intelligent enough to monitor the underlying data source for updates, re-querying automatically when the data is changed.

Conclusion

Since the introduction of Loaders 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. Loaders, 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 Loaders and the LoaderManager.

Don't forget to +1 this blog in the top right corner if you found this helpful!

Last updated January 18, 2014.



Understanding the LoaderManager (part 2)

Posted Jul 22, 2012

by Alex Lockwood

This post introduces the LoaderManager class. This is the second of a series of posts I will be writing on Loaders and the LoaderManager:

Note: Understanding the LoaderManager requires some general knowledge about how Loaders work. Their implementation will be covered extensively in my next post. For now, you should think of Loaders as simple, self-contained objects that (1) load data on a separate thread, and (2) monitor the underlying data source for updates, re-querying when changes are detected. This is more than enough to get you through the contents of this post. All Loaders are assumed to be 100% correctly implemented in this post.

What is the LoaderManager?

Simply stated, the LoaderManager is responsible for managing one or moreLoaders associated with an Activity or Fragment. Each Activity and each Fragment has exactly one LoaderManager instance that is in charge of starting, stopping, retaining, restarting, and destroying its Loaders. These events are sometimes initiated directly by the client, by calling initLoader(),restartLoader(), or destroyLoader(). Just as often, however, these events are triggered by major Activity/Fragment lifecycle events. For example, when an Activity is destroyed, the Activity instructs its LoaderManager to destroy and close its Loaders (as well as any resources associated with them, such as a Cursor).

The LoaderManager does not know how data is loaded, nor does it need to. Rather, the LoaderManager instructs its Loaders when to start/stop/reset their load, retaining their state across configuration changes and providing a simple interface for delivering results back to the client. In this way, the LoaderManager is a much more intelligent and generic implementation of the now-deprecated startManagingCursor method. While both manage data across the twists and turns of the Activity lifecycle, the LoaderManager is far superior for several reasons:

  • startManagingCursor manages Cursors, whereas the LoaderManager manages Loader<D> objects. The advantage here is that Loader<D> is generic, where D is the container object that holds the loaded data. In other words, the data source doesn't have to be a Cursor; it could be aList, a JSONArray... anything. The LoaderManager is independent of the container object that holds the data and is much more flexible as a result.

  • Calling startManagingCursor will make the Activity call requery() on the managed cursor. As mentioned in the previous post, requery() is a potentially expensive operation that is performed on the main UI thread. Subclasses of the Loader<D> class, on the other hand, are expected to load their data asynchronously, so using the LoaderManager will never block the UI thread.

  • startManagingCursor does not retain the Cursor's state across configuration changes. Instead, each time the Activity is destroyed due to a configuration change (a simple orientation change, for example), the Cursor is destroyed and must be requeried. The LoaderManager is much more intelligent in that it retains its Loaders' state across configuration changes, and thus doesn't need to requery its data.

  • The LoaderManager provides seamless monitoring of data! Whenever the Loader's data source is modified, the LoaderManager will receive a new asynchronous load from the corresponding Loader, and will return the updated data to the client. (Note: the LoaderManager will only be notified of these changes if the Loader is implemented correctly. We will discuss how to implement custom Loaders in part 3 of this series of posts).

If you feel overwhelmed by the details above, I wouldn't stress over it. The most important thing to take away from this is that the LoaderManager makes your life easy. It initializes, manages, and destroys Loaders for you, reducing both coding complexity and subtle lifecycle-related bugs in your Activitys and Fragments. Further, interacting with the LoaderManager involves implementing three simple callback methods. We discuss theLoaderManager.LoaderCallbacks<D> in the next section.

Implementing the LoaderManager.LoaderCallbacks<D>Interface

The LoaderManager.LoaderCallbacks<D> interface is a simple contract that the LoaderManager uses to report data back to the client. Each Loader gets its own callback object that the LoaderManager will interact with. This callback object fills in the gaps of the abstract LoaderManager implementation, telling it how to instantiate the Loader (onCreateLoader) and providing instructions when its load is complete/reset (onLoadFinished and onLoadReset, respectively). Most often you will implement the callbacks as part of the component itself, by having your Activity or Fragment implement theLoaderManager.LoaderCallbacks<D> interface:

public class SampleActivity extends Activity implements LoaderManager.LoaderCallbacks<D> {

 public Loader<D> onCreateLoader(int id, Bundle args) { ... }

 public void onLoadFinished(Loader<D> loader, D data) { ... }

 public void onLoaderReset(Loader<D> loader) { ... }

 /* ... */
}

Once instantiated, the client passes the callbacks object ("this", in this case) as the third argument to the LoaderManager's initLoader method, and will be bound to the Loader as soon as it is created.

Overall, implementing the callbacks is straightforward. Each callback method serves a specific purpose that makes interacting with the LoaderManager easy:

  • onCreateLoader is a factory method that simply returns a new Loader. The LoaderManager will call this method when it first creates the Loader.

  • onLoadFinished is called automatically when a Loader has finished its load. This method is typically where the client will update the application's UI with the loaded data. The client may (and should) assume that new data will be returned to this method each time new data is made available. Remember that it is the Loader's job to monitor the data source and to perform the actual asynchronous loads. The LoaderManager will receive these loads once they have completed, and then pass the result to the callback object's onLoadFinishedmethod for the client (i.e. the Activity/Fragment) to use.

  • Lastly, onLoadReset is called when the Loader's data is about to be reset. This method gives you the opportunity to remove any references to old data that may no longer be available.

In the next section, we will discuss a commonly asked question from beginning Android developers: how to transition from outdated managed Cursors to the much more powerful LoaderManager.

Transitioning from Managed Cursors to the LoaderManager

The code below is similar in behavior to the sample in my previous post. The difference, of course, is that it has been updated to use the LoaderManager. The CursorLoader ensures that all queries are performed asynchronously, thus guaranteeing that we won't block the UI thread. Further, the LoaderManager manages the CursorLoader across the Activity lifecycle, retaining its data on configuration changes and directing each new data load to the callback'sonLoadFinished method, where the Activity is finally free to make use of the queried Cursor.

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

public class SampleListActivity extends ListActivity implements
   LoaderManager.LoaderCallbacks<Cursor> {

 private static final String[] PROJECTION = new String[] { "_id", "text_column" };

 // The loader's unique id. Loader ids are specific to the Activity or
 // Fragment in which they reside.
 private static final int LOADER_ID = 1;

 // The callbacks through which we will interact with the LoaderManager.
 private LoaderManager.LoaderCallbacks<Cursor> mCallbacks;

 // The adapter that binds our data to the ListView
 private SimpleCursorAdapter mAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   String[] dataColumns = { "text_column" };
   int[] viewIDs = { R.id.text_view };

   // Initialize the adapter. Note that we pass a 'null' Cursor as the
   // third argument. We will pass the adapter a Cursor only when the
   // data has finished loading for the first time (i.e. when the
   // LoaderManager delivers the data to onLoadFinished). Also note
   // that we have passed the '0' flag as the last argument. This
   // prevents the adapter from registering a ContentObserver for the
   // Cursor (the CursorLoader will do this for us!).
   mAdapter = new SimpleCursorAdapter(this, R.layout.list_item,
       null, dataColumns, viewIDs, 0);

   // Associate the (now empty) adapter with the ListView.
   setListAdapter(mAdapter);

   // The Activity (which implements the LoaderCallbacks<Cursor>
   // interface) is the callbacks object through which we will interact
   // with the LoaderManager. The LoaderManager uses this object to
   // instantiate the Loader and to notify the client when data is made
   // available/unavailable.
   mCallbacks = this;

   // Initialize the Loader with id '1' and callbacks 'mCallbacks'.
   // If the loader doesn't already exist, one is created. Otherwise,
   // the already created Loader is reused. In either case, the
   // LoaderManager will manage the Loader across the Activity/Fragment
   // lifecycle, will receive any new loads once they have completed,
   // and will report this new data back to the 'mCallbacks' object.
   LoaderManager lm = getLoaderManager();
   lm.initLoader(LOADER_ID, null, mCallbacks);
 }

 @Override
 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   // Create a new CursorLoader with the following query parameters.
   return new CursorLoader(SampleListActivity.this, CONTENT_URI,
       PROJECTION, null, null, null);
 }

 @Override
 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
   // A switch-case is useful when dealing with multiple Loaders/IDs
   switch (loader.getId()) {
     case LOADER_ID:
       // The asynchronous load is complete and the data
       // is now available for use. Only now can we associate
       // the queried Cursor with the SimpleCursorAdapter.
       mAdapter.swapCursor(cursor);
       break;
   }
   // The listview now displays the queried data.
 }

 @Override
 public void onLoaderReset(Loader<Cursor> loader) {
   // For whatever reason, the Loader's data is now unavailable.
   // Remove any references to the old data by replacing it with
   // a null Cursor.
   mAdapter.swapCursor(null);
 }
}

Conclusion

As its name suggests, the LoaderManager is responsible for managing Loaders across the Activity/Fragment lifecycle. The LoaderManager is simple and its implementation usually requires very little code. The tricky part is implementing the Loaders, the topic of the next post: Implementing Loaders (part 3).

Leave a comment if you have any questions, or just to let me know if this post helped or not! Don't forget to +1 this blog in the top right corner too! :)



Implementing Loaders (part 3)

Posted Aug 21, 2012

by Alex Lockwood

This post introduces the Loader<D> class as well as custom Loader implementations. This is the third of a series of posts I will be writing on Loaders and the LoaderManager:

First things first, if you haven’t read my previous two posts, I suggest you do so before continuing further. Here is a very brief summary of what this blog has covered so far. Life Before Loaders (part 1) described the flaws of the pre-Honeycomb 3.0 API and its tendency to perform lengthy queries on the main UI thread. These UI-unfriendly APIs resulted in unresponsive applications and were the primary motivation for introducing the Loader and the LoaderManager in Android 3.0. Understanding the LoaderManager (part 2)introduced the LoaderManager class and its role in delivering asynchronously loaded data to the client. The LoaderManager manages its Loaders across the Activity/Fragment lifecycle and can retain loaded data across configuration changes.

Loader Basics

Loaders are responsible for performing queries on a separate thread, monitoring the data source for changes, and delivering new results to a registered listener (usually the LoaderManager) when changes are detected. These characteristics make Loaders a powerful addition to the Android SDK for several reasons:

  1. They encapsulate the actual loading of data. The Activity/Fragment no longer needs to know how to load data. Instead, the Activity/Fragment delegates the task to the Loader, which carries out the request behind the scenes and has its results delivered back to the Activity/Fragment.

  2. They abstract out the idea of threads from the client. The Activity/Fragment does not need to worry about offloading queries to a separate thread, as the Loader will do this automatically. This reduces code complexity and eliminates potential thread-related bugs.

  3. They are entirely event-driven. Loaders monitor the underlying data source and automatically perform new loads for up-to-date results when changes are detected. This makes working with Loaders easy, as the client can simply trust that the Loader will auto-update its data on its own. All the Activity/Fragment has to do is initialize the Loader and respond to any results that might be delivered. Everything in between is done by the Loader.

Loaders are a somewhat advanced topic and may take some time getting used to. We begin by analyzing its four defining characteristics in the next section.

What Makes Up a Loader?

There are four characteristics which ultimately determine a Loader’s behavior:

  1. A task to perform the asynchronous load. To ensure that loads are done on a separate thread, subclasses should extend AsyncTaskLoader<D> as opposed to the Loader<D> class. AsyncTaskLoader<D> is an abstract Loader which provides an AsyncTask to do its work. When subclassed, implementing the asynchronous task is as simple as implementing the abstract loadInBackground() method, which is called on a worker thread to perform the data load.

  2. A registered listener to receive the Loader's results when it completes a load.1 For each of its Loaders, the LoaderManager registers anOnLoadCompleteListener<D> which will forward the Loader’s delivered results to the client with a call to onLoadFinished(Loader<D> loader, D result). Loaders should deliver results to these registered listeners with a call to Loader#deliverResult(D result).

  3. One of three2 distinct states. Any given Loader will either be in a started,stopped, or reset state:

    • Loaders in a started state execute loads and may deliver their results to the listener at any time. Started Loaders should monitor for changes and perform new loads when changes are detected. Once started, the Loader will remain in a started state until it is either stopped or reset. This is the only state in whichonLoadFinished will ever be called.

    • Loaders in a stopped state continue to monitor for changes but should not deliver results to the client. From a stopped state, the Loader may either be started or reset.

    • Loaders in a reset state should not execute new loads, should notdeliver new results, and should not monitor for changes. When a loader enters a reset state, it should invalidate and free any data associated with it for garbage collection (likewise, the client should make sure they remove any references to this data, since it will no longer be available). More often than not, reset Loaders will never be called again; however, in some cases they may be started, so they should be able to start running properly again if necessary.

  4. An observer to receive notifications when the data source has changed.Loaders should implement an observer of some sort (i.e. aContentObserver, a BroadcastReceiver, etc.) to monitor the underlying data source for changes. When a change is detected, the observer should call Loader#onContentChanged(), which will either (a) force a new load if the Loader is in a started state or, (b) raise a flag indicating that a change has been made so that if the Loader is ever started again, it will know that it should reload its data.

By now you should have a basic understanding of how Loaders work. If not, I suggest you let it sink in for a bit and come back later to read through once more (reading the documentation never hurts either!). That being said, let’s get our hands dirty with the actual code!

Implementing the Loader

As I stated earlier, there is a lot that you must keep in mind when implementing your own custom Loaders. Subclasses must implement loadInBackground()and should override onStartLoading(), onStopLoading(), onReset(),onCanceled(), and deliverResult(D results) to achieve a fully functioning Loader. Overriding these methods is very important as the LoaderManager will call them regularly depending on the state of the Activity/Fragment lifecycle. For example, when an Activity is first started, the Activity instructs the LoaderManager to start each of its Loaders in Activity#onStart(). If a Loader is not already started, the LoaderManager calls startLoading(), which puts the Loader in a started state and immediately calls the Loader’sonStartLoading() method. In other words, a lot of work that the LoaderManager does behind the scenes relies on the Loader being correctly implemented, so don’t take the task of implementing these methods lightly!

The code below serves as a template of what a Loader implementation typically looks like. The SampleLoader queries a list of SampleItem objects and delivers a List<SampleItem> to the client:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {

 // We hold a reference to the Loader’s data here.
 private List<SampleItem> mData;

 public SampleLoader(Context ctx) {
   // Loaders may be used across multiple Activitys (assuming they aren't
   // bound to the LoaderManager), so NEVER hold a reference to the context
   // directly. Doing so will cause you to leak an entire Activity's context.
   // The superclass constructor will store a reference to the Application
   // Context instead, and can be retrieved with a call to getContext().
   super(ctx);
 }

 /****************************************************/
 /** (1) A task that performs the asynchronous load **/
 /****************************************************/

 @Override
 public List<SampleItem> loadInBackground() {
   // This method is called on a background thread and should generate a
   // new set of data to be delivered back to the client.
   List<SampleItem> data = new ArrayList<SampleItem>();

   // TODO: Perform the query here and add the results to 'data'.

   return data;
 }

 /********************************************************/
 /** (2) Deliver the results to the registered listener **/
 /********************************************************/

 @Override
 public void deliverResult(List<SampleItem> data) {
   if (isReset()) {
     // The Loader has been reset; ignore the result and invalidate the data.
     releaseResources(data);
     return;
   }

   // Hold a reference to the old data so it doesn't get garbage collected.
   // We must protect it until the new data has been delivered.
   List<SampleItem> oldData = mData;
   mData = data;

   if (isStarted()) {
     // If the Loader is in a started state, deliver the results to the
     // client. The superclass method does this for us.
     super.deliverResult(data);
   }

   // Invalidate the old data as we don't need it any more.
   if (oldData != null && oldData != data) {
     releaseResources(oldData);
   }
 }

 /*********************************************************/
 /** (3) Implement the Loader’s state-dependent behavior **/
 /*********************************************************/

 @Override
 protected void onStartLoading() {
   if (mData != null) {
     // Deliver any previously loaded data immediately.
     deliverResult(mData);
   }

   // Begin monitoring the underlying data source.
   if (mObserver == null) {
     mObserver = new SampleObserver();
     // TODO: register the observer
   }

   if (takeContentChanged() || mData == null) {
     // When the observer detects a change, it should call onContentChanged()
     // on the Loader, which will cause the next call to takeContentChanged()
     // to return true. If this is ever the case (or if the current data is
     // null), we force a new load.
     forceLoad();
   }
 }

 @Override
 protected void onStopLoading() {
   // The Loader is in a stopped state, so we should attempt to cancel the
   // current load (if there is one).
   cancelLoad();

   // Note that we leave the observer as is. Loaders in a stopped state
   // should still monitor the data source for changes so that the Loader
   // will know to force a new load if it is ever started again.
 }

 @Override
 protected void onReset() {
   // Ensure the loader has been stopped.
   onStopLoading();

   // At this point we can release the resources associated with 'mData'.
   if (mData != null) {
     releaseResources(mData);
     mData = null;
   }

   // The Loader is being reset, so we should stop monitoring for changes.
   if (mObserver != null) {
     // TODO: unregister the observer
     mObserver = null;
   }
 }

 @Override
 public void onCanceled(List<SampleItem> data) {
   // Attempt to cancel the current asynchronous load.
   super.onCanceled(data);

   // The load has been canceled, so we should release the resources
   // associated with 'data'.
   releaseResources(data);
 }

 private void releaseResources(List<SampleItem> data) {
   // For a simple List, there is nothing to do. For something like a Cursor, we
   // would close it in this method. All resources associated with the Loader
   // should be released here.
 }

 /*********************************************************************/
 /** (4) Observer which receives notifications when the data changes **/
 /*********************************************************************/

 // NOTE: Implementing an observer is outside the scope of this post (this example
 // uses a made-up "SampleObserver" to illustrate when/where the observer should
 // be initialized).
 
 // The observer could be anything so long as it is able to detect content changes
 // and report them to the loader with a call to onContentChanged(). For example,
 // if you were writing a Loader which loads a list of all installed applications
 // on the device, the observer could be a BroadcastReceiver that listens for the
 // ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular
 // Loader whenever the receiver detects that a new application has been installed.
 // Please don’t hesitate to leave a comment if you still find this confusing! :)
 private SampleObserver mObserver;
}

Conclusion

I hope these posts were useful and gave you a better understanding of how Loaders and the LoaderManager work together to perform asynchronous, auto-updating queries. Remember that Loaders are your friends... if you use them, your app will benefit in both responsiveness and the amount of code you need to write to get everything working properly! Hopefully I could help lessen the learning curve a bit by detailing them out!

As always, please don’t hesitate to leave a comment if you have any questions! And don't forget to +1 this blog in the top right corner if you found it helpful!


1 You don't need to worry about registering a listener for your Loader unless you plan on using it without the LoaderManager. The LoaderManager will act as this "listener" and will forward any results that the Loader delivers to theLoaderCallbacks#onLoadFinished method.

2 Loaders may also be in an "abandoned" state. This is an optional intermediary state between "stopped" and "reset" and is not discussed here for the sake of brevity. That said, in my experience implementing onAbandon() is usually not necessary.

Last updated January 16, 2014.



Tutorial: AppListLoader (part 4)

Posted Sep 16, 2012

by Alex Lockwood

This will be my fourth and final post on Loaders and the LoaderManager. Let me know in the comments if they have been helpful! Links to my previous Loader-related posts are given below:

Due to public demand, I've written a sample application that illustrates how to correctly implement a custom Loader. The application is namedAppListLoader, and it is a simple demo application that queries and lists all installed applications on your Android device. The application is a modified, re-thought (and bug-free) extension of the LoaderCustom.java sample that is provided in the API Demos. The application uses an AppListLoader (a subclass of AsyncTaskLoader) to query its data, and the LoaderManager to manage the Loader across the Activity/Fragment lifecycle:

The AppListLoader registers two BroadcastReceivers which observe/listen for system-wide broadcasts that impact the underlying data source. TheInstalledAppsObserver listens for newly installed, updated, or removed applications, and the SystemLocaleObserver listens for locale changes. For example, if the user changes the language from English to Spanish, theSystemLocaleObserver will notify the AppListLoader to re-query its data so that the application can display each application's name in Spanish (assuming an alternate Spanish name has been provided). Click "Change language" in the options menu and watch the Loader's seamless reaction to the event (it's awesome, isn't it? :P).

Log messages are written to the logcat whenever an important Loader/LoaderManager-related event occurs, so be sure to run the application while analyzing the logcat! Hopefully it'll give you a better understanding of how Loaders work in conjunction with the LoaderManager and the Activity/Fragment lifecycle. Be sure to filter the logcat by application name ("com.adp.loadercustom") for the best results!

You can download the application from Google Play by clicking the badge below:

The source code is available on GitHub. An excessive amount of comments flesh out the entire application-Loader workflow. Download it, import it as an eclipse project, and modify it all you want!

Let me know if these posts have been helpful by leaving a comment below! As always, don't hesitate to ask questions either!


http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值