to top
Android APIs
public abstract class

AsyncTaskLoader

extends Loader<D>
java.lang.Object
   ↳ android.content.Loader<D>
     ↳ android.content.AsyncTaskLoader<D>
Known Direct Subclasses

Class Overview

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);
    }
}

Summary

Public Constructors
AsyncTaskLoader(Context context)
Public Methods
void cancelLoadInBackground()
Called on the main thread to abort a load in progress.
void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)
Print the Loader's state into the given stream.
boolean isLoadInBackgroundCanceled()
Returns true if the current invocation of loadInBackground() is being canceled.
abstract D loadInBackground()
Called on a worker thread to perform the actual load and to return the result of the load operation.
void onCanceled(D data)
Called if the task was canceled before it was completed.
void setUpdateThrottle(long delayMS)
Set amount to throttle updates by.
Protected Methods
boolean onCancelLoad()
Subclasses must implement this to take care of requests to cancelLoad().
void onForceLoad()
Subclasses must implement this to take care of requests to forceLoad().
D onLoadInBackground()
[Expand]
Inherited Methods
From class android.content.Loader
From class java.lang.Object

Public Constructors

public AsyncTaskLoader (Context context)

Added in API level 11

Public Methods

public void cancelLoadInBackground ()

Added in API level 16

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.

public void dump (String prefix, FileDescriptor fd, PrintWriter writer, String[] args)

Added in API level 11

Print the Loader's state into the given stream.

Parameters
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.

public boolean isLoadInBackgroundCanceled ()

Added in API level 16

Returns true if the current invocation of loadInBackground() is being canceled.

Returns

public abstract D loadInBackground ()

Added in API level 11

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.

Returns
  • The result of the load operation.
Throws
OperationCanceledException if the load is canceled during execution.

public void onCanceled (D data)

Added in API level 11

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.

Parameters
data The value that was returned by loadInBackground(), or null if the task threw OperationCanceledException.

public void setUpdateThrottle (long delayMS)

Added in API level 11

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.

Parameters
delayMS Amount of delay, in milliseconds.

Protected Methods

protected boolean onCancelLoad ()

Added in API level 16

Subclasses must implement this to take care of requests to cancelLoad(). This will always be called from the process's main thread.

Returns
  • Returns false if the task could not be canceled, typically because it has already completed normally, or because 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.

protected void onForceLoad ()

Added in API level 11

Subclasses must implement this to take care of requests to forceLoad(). This will always be called from the process's main thread.

protected D onLoadInBackground ()

Added in API level 11

Calls loadInBackground(). This method is reserved for use by the loader framework. Subclasses should override loadInBackground() instead of this method.

Returns
  • The result of the load operation.
Throws
OperationCanceledException if the load is canceled during execution.