java.lang.Object | ||
↳ | android.content.Loader<D> | |
↳ | android.content.AsyncTaskLoader<D> |
Known Direct Subclasses |
Abstract Loader that provides an AsyncTask
to do the work. See
Loader
and LoaderManager
for more details.
Here is an example implementation of an AsyncTaskLoader subclass that loads the currently installed applications from the package manager. This implementation takes care of retrieving the application labels and sorting its result set from them, monitoring for changes to the installed applications, and rebuilding the list when a change in configuration requires this (such as a locale change).
/** * This class holds the per-item data in our Loader. */ public static class AppEntry { public AppEntry(AppListLoader loader, ApplicationInfo info) { mLoader = loader; mInfo = info; mApkFile = new File(info.sourceDir); } public ApplicationInfo getApplicationInfo() { return mInfo; } public String getLabel() { return mLabel; } public Drawable getIcon() { if (mIcon == null) { if (mApkFile.exists()) { mIcon = mInfo.loadIcon(mLoader.mPm); return mIcon; } else { mMounted = false; } } else if (!mMounted) { // If the app wasn't mounted but is now mounted, reload // its icon. if (mApkFile.exists()) { mMounted = true; mIcon = mInfo.loadIcon(mLoader.mPm); return mIcon; } } else { return mIcon; } return mLoader.getContext().getResources().getDrawable( android.R.drawable.sym_def_app_icon); } @Override public String toString() { return mLabel; } void loadLabel(Context context) { if (mLabel == null || !mMounted) { if (!mApkFile.exists()) { mMounted = false; mLabel = mInfo.packageName; } else { mMounted = true; CharSequence label = mInfo.loadLabel(context.getPackageManager()); mLabel = label != null ? label.toString() : mInfo.packageName; } } } private final AppListLoader mLoader; private final ApplicationInfo mInfo; private final File mApkFile; private String mLabel; private Drawable mIcon; private boolean mMounted; } /** * Perform alphabetical comparison of application entry objects. */ public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { private final Collator sCollator = Collator.getInstance(); @Override public int compare(AppEntry object1, AppEntry object2) { return sCollator.compare(object1.getLabel(), object2.getLabel()); } }; /** * Helper for determining if the configuration has changed in an interesting * way so we need to rebuild the app list. */ public static class InterestingConfigChanges { final Configuration mLastConfiguration = new Configuration(); int mLastDensity; boolean applyNewConfig(Resources res) { int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { mLastDensity = res.getDisplayMetrics().densityDpi; return true; } return false; } } /** * Helper class to look for interesting changes to the installed apps * so that the loader can be updated. */ public static class PackageIntentReceiver extends BroadcastReceiver { final AppListLoader mLoader; public PackageIntentReceiver(AppListLoader loader) { mLoader = loader; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mLoader.getContext().registerReceiver(this, filter); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mLoader.getContext().registerReceiver(this, sdFilter); } @Override public void onReceive(Context context, Intent intent) { // Tell the loader about the change. mLoader.onContentChanged(); } } /** * A custom Loader that loads all of the installed applications. */ public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> { final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); final PackageManager mPm; List<AppEntry> mApps; PackageIntentReceiver mPackageObserver; public AppListLoader(Context context) { super(context); // Retrieve the package manager for later use; note we don't // use 'context' directly but instead the save global application // context returned by getContext(). mPm = getContext().getPackageManager(); } /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */ @Override public List<AppEntry> loadInBackground() { // Retrieve all known applications. List<ApplicationInfo> apps = mPm.getInstalledApplications( PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS); if (apps == null) { apps = new ArrayList<ApplicationInfo>(); } final Context context = getContext(); // Create corresponding array of entries and load their labels. List<AppEntry> entries = new ArrayList<AppEntry>(apps.size()); for (int i=0; i<apps.size(); i++) { AppEntry entry = new AppEntry(this, apps.get(i)); entry.loadLabel(context); entries.add(entry); } // Sort the list. Collections.sort(entries, ALPHA_COMPARATOR); // Done! return entries; } /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */ @Override public void deliverResult(List<AppEntry> apps) { if (isReset()) { // An async query came in while the loader is stopped. We // don't need the result. if (apps != null) { onReleaseResources(apps); } } List<AppEntry> oldApps = mApps; mApps = apps; if (isStarted()) { // If the Loader is currently started, we can immediately // deliver its results. super.deliverResult(apps); } // At this point we can release the resources associated with // 'oldApps' if needed; now that the new result is delivered we // know that it is no longer in use. if (oldApps != null) { onReleaseResources(oldApps); } } /** * Handles a request to start the Loader. */ @Override protected void onStartLoading() { if (mApps != null) { // If we currently have a result available, deliver it // immediately. deliverResult(mApps); } // Start watching for changes in the app data. if (mPackageObserver == null) { mPackageObserver = new PackageIntentReceiver(this); } // Has something interesting in the configuration changed since we // last built the app list? boolean configChange = mLastConfig.applyNewConfig(getContext().getResources()); if (takeContentChanged() || mApps == null || configChange) { // If the data has changed since the last time it was loaded // or is not currently available, start a load. forceLoad(); } } /** * Handles a request to stop the Loader. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to cancel a load. */ @Override public void onCanceled(List<AppEntry> apps) { super.onCanceled(apps); // At this point we can release the resources associated with 'apps' // if needed. onReleaseResources(apps); } /** * Handles a request to completely reset the Loader. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'apps' // if needed. if (mApps != null) { onReleaseResources(mApps); mApps = null; } // Stop monitoring for changes. if (mPackageObserver != null) { getContext().unregisterReceiver(mPackageObserver); mPackageObserver = null; } } /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */ protected void onReleaseResources(List<AppEntry> apps) { // For a simple List<> there is nothing to do. For something // like a Cursor, we would close it here. } }
An example implementation of a fragment that uses the above loader to show the currently installed applications in a list is below.
public static class AppListAdapter extends ArrayAdapter<AppEntry> { private final LayoutInflater mInflater; public AppListAdapter(Context context) { super(context, android.R.layout.simple_list_item_2); mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void setData(List<AppEntry> data) { clear(); if (data != null) { addAll(data); } } /** * Populate new items in the list. */ @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { view = mInflater.inflate(R.layout.list_item_icon_text, parent, false); } else { view = convertView; } AppEntry item = getItem(position); ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon()); ((TextView)view.findViewById(R.id.text)).setText(item.getLabel()); return view; } } public static class AppListFragment extends ListFragment implements OnQueryTextListener, OnCloseListener, LoaderManager.LoaderCallbacks<List<AppEntry>> { // This is the Adapter being used to display the list's data. AppListAdapter mAdapter; // The SearchView for doing filtering. SearchView mSearchView; // 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 applications"); // 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 AppListAdapter(getActivity()); setListAdapter(mAdapter); // Start out with a progress indicator. setListShown(false); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } public static class MySearchView extends SearchView { public MySearchView(Context context) { super(context); } // The normal SearchView doesn't clear its search text when // collapsed, so we will do this for it. @Override public void onActionViewCollapsed() { setQuery("", false); super.onActionViewCollapsed(); } } @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 | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); mSearchView = new MySearchView(getActivity()); mSearchView.setOnQueryTextListener(this); mSearchView.setOnCloseListener(this); mSearchView.setIconifiedByDefault(true); item.setActionView(mSearchView); } @Override public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Since this // is a simple array adapter, we can just have it do the filtering. mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; mAdapter.getFilter().filter(mCurFilter); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public boolean onClose() { if (!TextUtils.isEmpty(mSearchView.getQuery())) { mSearchView.setQuery(null, true); } return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("LoaderCustom", "Item clicked: " + id); } @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader with no arguments, so it is simple. return new AppListLoader(getActivity()); } @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) { // Set the new data in the adapter. mAdapter.setData(data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } @Override public void onLoaderReset(Loader<List<AppEntry>> loader) { // Clear the data in the adapter. mAdapter.setData(null); } }
Public Constructors | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Called on the main thread to abort a load in progress.
| |||||||||||
Print the Loader's state into the given stream.
| |||||||||||
Returns true if the current invocation of
loadInBackground() is being canceled. | |||||||||||
Called on a worker thread to perform the actual load and to return
the result of the load operation.
| |||||||||||
Called if the task was canceled before it was completed.
| |||||||||||
Set amount to throttle updates by.
|
Protected Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Subclasses must implement this to take care of requests to
cancelLoad() . | |||||||||||
Subclasses must implement this to take care of requests to
forceLoad() . | |||||||||||
Calls
loadInBackground() . |
[Expand]
Inherited Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
From class
android.content.Loader
| |||||||||||
From class
java.lang.Object
|
Called on the main thread to abort a load in progress.
Override this method to abort the current invocation of loadInBackground()
that is running in the background on a worker thread.
This method should do nothing if loadInBackground()
has not started
running or if it has already finished.
Print the Loader's state into the given stream.
prefix | Text to print at the front of each line. |
---|---|
fd | The raw file descriptor that the dump is being sent to. |
writer | A PrintWriter to which the dump is to be set. |
args | Additional arguments to the dump request. |
Returns true if the current invocation of loadInBackground()
is being canceled.
loadInBackground()
is being canceled.Called on a worker thread to perform the actual load and to return
the result of the load operation.
Implementations should not deliver the result directly, but should return them
from this method, which will eventually end up calling deliverResult(D)
on
the UI thread. If implementations need to process the results on the UI thread
they may override deliverResult(D)
and do so there.
To support cancellation, this method should periodically check the value of
isLoadInBackgroundCanceled()
and terminate when it returns true.
Subclasses may also override cancelLoadInBackground()
to interrupt the load
directly instead of polling isLoadInBackgroundCanceled()
.
When the load is canceled, this method may either return normally or throw
OperationCanceledException
. In either case, the Loader
will
call onCanceled(D)
to perform post-cancellation cleanup and to dispose of the
result object, if any.
OperationCanceledException | if the load is canceled during execution. |
---|
Called if the task was canceled before it was completed. Gives the class a chance to clean up post-cancellation and to properly dispose of the result.
data | The value that was returned by loadInBackground() , or null
if the task threw OperationCanceledException .
|
---|
Set amount to throttle updates by. This is the minimum time from
when the last loadInBackground()
call has completed until
a new load is scheduled.
delayMS | Amount of delay, in milliseconds. |
---|
Subclasses must implement this to take care of requests to cancelLoad()
.
This will always be called from the process's main thread.
startLoading()
hasn't been called; returns
true otherwise. When true is returned, the task
is still running and the Loader.OnLoadCanceledListener
will be called
when the task completes.
Subclasses must implement this to take care of requests to forceLoad()
.
This will always be called from the process's main thread.
Calls loadInBackground()
.
This method is reserved for use by the loader framework.
Subclasses should override loadInBackground()
instead of this method.
OperationCanceledException | if the load is canceled during execution. |
---|