Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:
Activity
and Fragment
.There are multiple classes and interfaces that may be involved in using loaders in an application. They are summarized in this table:
Class/Interface | Description |
---|---|
LoaderManager |
An abstract class associated with an Activity or
Fragment for managing one or more Loader instances. This helps an application manage
longer-running operations in conjunction with the Activity
or Fragment lifecycle; the most common use of this is with a
CursorLoader , however applications are free to write
their own loaders for loading other types of data.
There is only one LoaderManager per activity or fragment. But a LoaderManager can have
multiple loaders. |
LoaderManager.LoaderCallbacks |
A callback interface for a client to interact with the LoaderManager . For example, you use the onCreateLoader()
callback method to create a new loader. |
Loader |
An abstract class that performs asynchronous loading of data. This is
the base class for a loader. You would typically use CursorLoader , but you can implement your own subclass. While
loaders are active they should monitor the source of their data and deliver new
results when the contents change. |
AsyncTaskLoader |
Abstract loader that provides an AsyncTask to do the work. |
CursorLoader |
A subclass of AsyncTaskLoader that queries the
ContentResolver and returns a Cursor . This class implements the Loader protocol in a standard way for querying cursors,
building on AsyncTaskLoader to perform the cursor query
on a background thread so that it does not block the application's UI. Using
this loader is the best way to asynchronously load data from a ContentProvider , instead of performing a managed query through
the fragment or activity's APIs. |
The classes and interfaces in the above table are the essential components
you'll use to implement a loader in your application. You won't need all of them
for each loader you create, but you'll always need a reference to the LoaderManager
in order to initialize a loader and an implementation
of a Loader
class such as CursorLoader
. The following sections show you how to use these
classes and interfaces in an application.
This section describes how to use loaders in an Android application. An application that uses loaders typically includes the following:
Activity
or Fragment
.LoaderManager
.CursorLoader
to load data backed by a ContentProvider
. Alternatively, you can implement your own subclass
of Loader
or AsyncTaskLoader
to
load data from some other source.LoaderManager.LoaderCallbacks
.
This is where you create new loaders and manage your references to existing
loaders.SimpleCursorAdapter
.ContentProvider
, when using a
CursorLoader
.The LoaderManager
manages one or more Loader
instances within an Activity
or
Fragment
. There is only one LoaderManager
per activity or fragment.
You typically
initialize a Loader
within the activity's onCreate()
method, or within the fragment's
onActivityCreated()
method. You
do this as follows:
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this);
The initLoader()
method takes
the following parameters:
null
in this example).LoaderManager.LoaderCallbacks
implementation, which
the LoaderManager
calls to report loader events. In this
example, the local class implements the LoaderManager.LoaderCallbacks
interface, so it passes a reference
to itself, this
.The initLoader()
call ensures that a loader
is initialized and active. It has two possible outcomes:
initLoader()
triggers the
LoaderManager.LoaderCallbacks
method onCreateLoader()
.
This is where you implement the code to instantiate and return a new loader.
For more discussion, see the section onCreateLoader.In either case, the given LoaderManager.LoaderCallbacks
implementation is associated with the loader, and will be called when the
loader state changes. If at the point of this call the caller is in its
started state, and the requested loader already exists and has generated its
data, then the system calls onLoadFinished()
immediately (during initLoader()
),
so you must be prepared for this to happen. See
onLoadFinished for more discussion of this callback
Note that the initLoader()
method returns the Loader
that is created, but you don't
need to capture a reference to it. The LoaderManager
manages
the life of the loader automatically. The LoaderManager
starts and stops loading when necessary, and maintains the state of the loader
and its associated content. As this implies, you rarely interact with loaders
directly (though for an example of using loader methods to fine-tune a loader's
behavior, see the LoaderThrottle sample).
You most commonly use the LoaderManager.LoaderCallbacks
methods to intervene in the loading
process when particular events occur. For more discussion of this topic, see Using the LoaderManager Callbacks.
When you use initLoader()
, as
shown above, it uses an existing loader with the specified ID if there is one.
If there isn't, it creates one. But sometimes you want to discard your old data
and start over.
To discard your old data, you use restartLoader()
. For example, this
implementation of SearchView.OnQueryTextListener
restarts
the loader when the user's query changes. The loader needs to be restarted so
that it can use the revised search filter to do a new query:
public boolean onQueryTextChanged(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; }
LoaderManager.LoaderCallbacks
is a callback interface
that lets a client interact with the LoaderManager
.
Loaders, in particular CursorLoader
, are expected to
retain their data after being stopped. This allows applications to keep their
data across the activity or fragment's onStop()
and onStart()
methods, so that
when users return to an application, they don't have to wait for the data to
reload. You use the LoaderManager.LoaderCallbacks
methods
when to know when to create a new loader, and to tell the application when it is
time to stop using a loader's data.
LoaderManager.LoaderCallbacks
includes these
methods:
onCreateLoader()
—
Instantiate and return a new Loader
for the given ID.
onLoadFinished()
— Called when a previously created loader has finished its load.
onLoaderReset()
— Called when a previously created loader is being reset, thus making its
data unavailable.
These methods are described in more detail in the following sections.
When you attempt to access a loader (for example, through initLoader()
), it checks to see whether
the loader specified by the ID exists. If it doesn't, it triggers the LoaderManager.LoaderCallbacks
method onCreateLoader()
. This
is where you create a new loader. Typically this will be a CursorLoader
, but you can implement your own Loader
subclass.
In this example, the onCreateLoader()
callback method creates a CursorLoader
. You must build
the CursorLoader
using its constructor method, which
requires the complete set of information needed to perform a query to the ContentProvider
. Specifically, it needs:
null
will return all columns, which is inefficient. null
will return all rows for the given URI. null
will
use the default sort order, which may be unordered.For example:
// If non-null, this is the current filter the user has provided. String mCurFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
This method is called when a previously created loader has finished its load. This method is guaranteed to be called prior to the release of the last data that was supplied for this loader. At this point you should remove all use of the old data (since it will be released soon), but should not do your own release of the data since its loader owns it and will take care of that.
The loader will release the data once it knows the application is no longer
using it. For example, if the data is a cursor from a CursorLoader
, you should not call close()
on it yourself. If the cursor is being
placed in a CursorAdapter
, you should use the swapCursor()
method so that the
old Cursor
is not closed. For example:
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); }
This method is called when a previously created loader is being reset, thus making its data unavailable. This callback lets you find out when the data is about to be released so you can remove your reference to it.
This implementation calls
swapCursor()
with a value of null
:
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); }
As an example, here is the full implementation of a Fragment
that displays a ListView
containing
the results of a query against the contacts content provider. It uses a CursorLoader
to manage the query on the provider.
For an application to access a user's contacts, as shown in this example, its
manifest must include the permission
READ_CONTACTS
.
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; // If non-null, this is the current filter the user has provided. String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText("No phone numbers"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } // These are the Contacts rows that we will retrieve. static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } }
There are a few different samples in ApiDemos that illustrate how to use loaders:
For information on downloading and installing the SDK samples, see Getting the Samples.