diff options
Diffstat (limited to 'docs/html/guide/components/loaders.jd')
-rw-r--r-- | docs/html/guide/components/loaders.jd | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/docs/html/guide/components/loaders.jd b/docs/html/guide/components/loaders.jd new file mode 100644 index 0000000..ddd513b --- /dev/null +++ b/docs/html/guide/components/loaders.jd @@ -0,0 +1,500 @@ +page.title=Loaders +parent.title=Activities +parent.link=activities.html +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + <h2>In this document</h2> + <ol> + <li><a href="#summary">Loader API Summary</a></li> + <li><a href="#app">Using Loaders in an Application</a> + <ol> + <li><a href="#requirements"></a></li> + <li><a href="#starting">Starting a Loader</a></li> + <li><a href="#restarting">Restarting a Loader</a></li> + <li><a href="#callback">Using the LoaderManager Callbacks</a></li> + </ol> + </li> + <li><a href="#example">Example</a> + <ol> + <li><a href="#more_examples">More Examples</a></li> + </ol> + </li> + </ol> + + <h2>Key classes</h2> + <ol> + <li>{@link android.app.LoaderManager}</li> + <li>{@link android.content.Loader}</li> + + </ol> + + <h2>Related samples</h2> + <ol> + <li> <a +href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html"> +LoaderCursor</a></li> + <li> <a +href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> +LoaderThrottle</a></li> + </ol> + </div> +</div> + +<p>Introduced in Android 3.0, loaders make it easy to asynchronously load data +in an activity or fragment. Loaders have these characteristics:</p> + <ul> + <li>They are available to every {@link android.app.Activity} and {@link +android.app.Fragment}.</li> + <li>They provide asynchronous loading of data.</li> + <li>They monitor the source of their data and deliver new results when the +content changes.</li> + <li>They automatically reconnect to the last loader's cursor when being +recreated after a configuration change. Thus, they don't need to re-query their +data.</li> + </ul> + +<h2 id="summary">Loader API Summary</h2> + +<p>There are multiple classes and interfaces that may be involved in using +loaders in an application. They are summarized in this table:</p> + +<table> + <tr> + <th>Class/Interface</th> + <th>Description</th> + </tr> + <tr> + <td>{@link android.app.LoaderManager}</td> + <td>An abstract class associated with an {@link android.app.Activity} or +{@link android.app.Fragment} for managing one or more {@link +android.content.Loader} instances. This helps an application manage +longer-running operations in conjunction with the {@link android.app.Activity} +or {@link android.app.Fragment} lifecycle; the most common use of this is with a +{@link android.content.CursorLoader}, however applications are free to write +their own loaders for loading other types of data. + <br /> + <br /> + There is only one {@link android.app.LoaderManager} per activity or fragment. But a {@link android.app.LoaderManager} can have +multiple loaders.</td> + </tr> + <tr> + <td>{@link android.app.LoaderManager.LoaderCallbacks}</td> + <td>A callback interface for a client to interact with the {@link +android.app.LoaderManager}. For example, you use the {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +callback method to create a new loader.</td> + </tr> + <tr> + <td>{@link android.content.Loader}</td> + <td>An abstract class that performs asynchronous loading of data. This is +the base class for a loader. You would typically use {@link +android.content.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. </td> + </tr> + <tr> + <td>{@link android.content.AsyncTaskLoader}</td> + <td>Abstract loader that provides an {@link android.os.AsyncTask} to do the work.</td> + </tr> + <tr> + <td>{@link android.content.CursorLoader}</td> + <td>A subclass of {@link android.content.AsyncTaskLoader} that queries the +{@link android.content.ContentResolver} and returns a {@link +android.database.Cursor}. This class implements the {@link +android.content.Loader} protocol in a standard way for querying cursors, +building on {@link android.content.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 {@link +android.content.ContentProvider}, instead of performing a managed query through +the fragment or activity's APIs.</td> + </tr> +</table> + +<p>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 {@link +android.app.LoaderManager} in order to initialize a loader and an implementation +of a {@link android.content.Loader} class such as {@link +android.content.CursorLoader}. The following sections show you how to use these +classes and interfaces in an application.</p> + +<h2 id ="app">Using Loaders in an Application</h2> +<p>This section describes how to use loaders in an Android application. An +application that uses loaders typically includes the following:</p> +<ul> + <li>An {@link android.app.Activity} or {@link android.app.Fragment}.</li> + <li>An instance of the {@link android.app.LoaderManager}.</li> + <li>A {@link android.content.CursorLoader} to load data backed by a {@link +android.content.ContentProvider}. Alternatively, you can implement your own subclass +of {@link android.content.Loader} or {@link android.content.AsyncTaskLoader} to +load data from some other source.</li> + <li>An implementation for {@link android.app.LoaderManager.LoaderCallbacks}. +This is where you create new loaders and manage your references to existing +loaders.</li> +<li>A way of displaying the loader's data, such as a {@link +android.widget.SimpleCursorAdapter}.</li> + <li>A data source, such as a {@link android.content.ContentProvider}, when using a +{@link android.content.CursorLoader}.</li> +</ul> +<h3 id="starting">Starting a Loader</h3> + +<p>The {@link android.app.LoaderManager} manages one or more {@link +android.content.Loader} instances within an {@link android.app.Activity} or +{@link android.app.Fragment}. There is only one {@link +android.app.LoaderManager} per activity or fragment.</p> + +<p>You typically +initialize a {@link android.content.Loader} within the activity's {@link +android.app.Activity#onCreate onCreate()} method, or within the fragment's +{@link android.app.Fragment#onActivityCreated onActivityCreated()} method. You +do this as follows:</p> + +<pre>// Prepare the loader. Either re-connect with an existing one, +// or start a new one. +getLoaderManager().initLoader(0, null, this);</pre> + +<p>The {@link android.app.LoaderManager#initLoader initLoader()} method takes +the following parameters:</p> +<ul> + <li>A unique ID that identifies the loader. In this example, the ID is 0.</li> +<li>Optional arguments to supply to the loader at +construction (<code>null</code> in this example).</li> + +<li>A {@link android.app.LoaderManager.LoaderCallbacks} implementation, which +the {@link android.app.LoaderManager} calls to report loader events. In this +example, the local class implements the {@link +android.app.LoaderManager.LoaderCallbacks} interface, so it passes a reference +to itself, {@code this}.</li> +</ul> +<p>The {@link android.app.LoaderManager#initLoader initLoader()} call ensures that a loader +is initialized and active. It has two possible outcomes:</p> +<ul> + <li>If the loader specified by the ID already exists, the last created loader +is reused.</li> + <li>If the loader specified by the ID does <em>not</em> exist, +{@link android.app.LoaderManager#initLoader initLoader()} triggers the +{@link android.app.LoaderManager.LoaderCallbacks} method {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. +This is where you implement the code to instantiate and return a new loader. +For more discussion, see the section <a +href="#onCreateLoader">onCreateLoader</a>.</li> +</ul> +<p>In either case, the given {@link android.app.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 {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +immediately (during {@link android.app.LoaderManager#initLoader initLoader()}), +so you must be prepared for this to happen. See <a href="#onLoadFinished"> +onLoadFinished</a> for more discussion of this callback</p> + +<p>Note that the {@link android.app.LoaderManager#initLoader initLoader()} +method returns the {@link android.content.Loader} that is created, but you don't +need to capture a reference to it. The {@link android.app.LoaderManager} manages +the life of the loader automatically. The {@link android.app.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 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a> sample). +You most commonly use the {@link +android.app.LoaderManager.LoaderCallbacks} methods to intervene in the loading +process when particular events occur. For more discussion of this topic, see <a +href="#callback">Using the LoaderManager Callbacks</a>.</p> + +<h3 id="restarting">Restarting a Loader</h3> + +<p>When you use {@link android.app.LoaderManager#initLoader 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.</p> + +<p>To discard your old data, you use {@link +android.app.LoaderManager#restartLoader restartLoader()}. For example, this +implementation of {@link android.widget.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:</p> + +<pre> +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; +}</pre> + +<h3 id="callback">Using the LoaderManager Callbacks</h3> + +<p>{@link android.app.LoaderManager.LoaderCallbacks} is a callback interface +that lets a client interact with the {@link android.app.LoaderManager}. </p> +<p>Loaders, in particular {@link android.content.CursorLoader}, are expected to +retain their data after being stopped. This allows applications to keep their +data across the activity or fragment's {@link android.app.Activity#onStop +onStop()} and {@link android.app.Activity#onStart onStart()} methods, so that +when users return to an application, they don't have to wait for the data to +reload. You use the {@link android.app.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.</p> + +<p>{@link android.app.LoaderManager.LoaderCallbacks} includes these +methods:</p> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — +Instantiate and return a new {@link android.content.Loader} for the given ID. +</li></ul> +<ul> + <li> {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +— Called when a previously created loader has finished its load. +</li></ul> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} + — Called when a previously created loader is being reset, thus making its +data unavailable. +</li> +</ul> +<p>These methods are described in more detail in the following sections.</p> + +<h4 id ="onCreateLoader">onCreateLoader</h4> + +<p>When you attempt to access a loader (for example, through {@link +android.app.LoaderManager#initLoader initLoader()}), it checks to see whether +the loader specified by the ID exists. If it doesn't, it triggers the {@link +android.app.LoaderManager.LoaderCallbacks} method {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. This +is where you create a new loader. Typically this will be a {@link +android.content.CursorLoader}, but you can implement your own {@link +android.content.Loader} subclass. </p> + +<p>In this example, the {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +callback method creates a {@link android.content.CursorLoader}. You must build +the {@link android.content.CursorLoader} using its constructor method, which +requires the complete set of information needed to perform a query to the {@link +android.content.ContentProvider}. Specifically, it needs:</p> +<ul> + <li><em>uri</em> — The URI for the content to retrieve. </li> + <li><em>projection</em> — A list of which columns to return. Passing +<code>null</code> will return all columns, which is inefficient. </li> + <li><em>selection</em> — A filter declaring which rows to return, +formatted as an SQL WHERE clause (excluding the WHERE itself). Passing +<code>null</code> will return all rows for the given URI. </li> + <li><em>selectionArgs</em> — You may include ?s in the selection, which will +be replaced by the values from <em>selectionArgs</em>, in the order that they appear in +the selection. The values will be bound as Strings. </li> + <li><em>sortOrder</em> — How to order the rows, formatted as an SQL +ORDER BY clause (excluding the ORDER BY itself). Passing <code>null</code> will +use the default sort order, which may be unordered.</li> +</ul> +<p>For example:</p> +<pre> + // 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"); +}</pre> +<h4 id="onLoadFinished">onLoadFinished</h4> + +<p>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.</p> + + +<p>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 {@link +android.content.CursorLoader}, you should not call {@link +android.database.Cursor#close close()} on it yourself. If the cursor is being +placed in a {@link android.widget.CursorAdapter}, you should use the {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} method so that the +old {@link android.database.Cursor} is not closed. For example:</p> + +<pre> +// This is the Adapter being used to display the list's data.<br +/>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); +}</pre> + +<h4 id="onLoaderReset">onLoaderReset</h4> + +<p>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. </p> +<p>This implementation calls +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +with a value of <code>null</code>:</p> + +<pre> +// 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); +}</pre> + + +<h2 id="example">Example</h2> + +<p>As an example, here is the full implementation of a {@link +android.app.Fragment} that displays a {@link android.widget.ListView} containing +the results of a query against the contacts content provider. It uses a {@link +android.content.CursorLoader} to manage the query on the provider.</p> + +<p>For an application to access a user's contacts, as shown in this example, its +manifest must include the permission +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.</p> + +<pre> +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); + } +}</pre> +<h3 id="more_examples">More Examples</h3> + +<p>There are a few different samples in <strong>ApiDemos</strong> that +illustrate how to use loaders:</p> +<ul> + <li><a +href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html"> +LoaderCursor</a> — A complete version of the +snippet shown above.</li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a> — An example of how to use throttling to +reduce the number of queries a content provider does when its data changes.</li> +</ul> + +<p>For information on downloading and installing the SDK samples, see <a +href="http://developer.android.com/resources/samples/get.html"> Getting the +Samples</a>. </p> + |