7.1: AsyncTask and AsyncTaskLoader

Contents:

There are several ways to do background processing in Android. Two of those ways are:

  • You can do background processing directly, using the AsyncTask class.
  • You can do background processing indirectly, using the Loader framework and then the AsyncTaskLoader class.

In most situations the Loader framework is a better choice, but it's important to know how AsyncTask works.

Note: The Loader framework provides special-purpose classes that manage loading and reloading updated data asynchronously in the background. You learn more about loaders in a later lesson.

In this chapter you learn why it's important to process some tasks in the background, off the UI thread. You learn how to use AsyncTask, when not to use AsyncTask, and the basics of using loaders.

The UI thread

When an Android app starts, it creates the main thread, which is often called the UI thread. The UI thread dispatches events to the appropriate user interface (UI) widgets. The UI thread is where your app interacts with components from the Android UI toolkit (components from the android.widget and android.view packages).

Android's thread model has two rules:

  • Do not block the UI thread.
  • Do UI work only on the UI thread.

The UI thread needs to give its attention to drawing the UI and keeping the app responsive to user input. If everything happened on the UI thread, long operations such as network access or database queries could block the whole UI. From the user's perspective, the app would appear to hang. Even worse, if the UI thread were blocked for more than a few seconds (about 5 seconds currently) the user would be presented with the "application not responding" (ANR) dialog. The user might decide to quit your app and uninstall it.

To make sure your app doesn't block the UI thread:

  • Complete all work in less than 16 ms for each UI screen.
  • Don't run asynchronous tasks and other long-running tasks on the UI thread. Instead, implement tasks on a background thread using AsyncTask (for short or interruptible tasks) or AsyncTaskLoader (for tasks that are high-priority, or tasks that need to report back to the user or UI).

Conversely, don't use a background thread to manipulate your UI, because the Android UI toolkit is not thread-safe.

AsyncTask

A worker thread is any thread which is not the main or UI thread. Use the AsyncTask class to implement an asynchronous, long-running task on a worker thread. AsyncTask allows you to perform background operations on a worker thread and publish results on the UI thread without needing to directly manipulate threads or handlers.

When AsyncTask is executed, it goes through four steps:

  1. onPreExecute() is invoked on the UI thread before the task is executed. This step is normally used to set up the task, for instance by showing a progress bar in the UI.
  2. doInBackground(Params...) is invoked on the background thread immediately after onPreExecute() finishes. This step performs a background computation, returns a result, and passes the result to onPostExecute(). The doInBackground() method can also call publishProgress(Progress...) to publish one or more units of progress.
  3. onProgressUpdate(Progress...) runs on the UI thread after publishProgress(Progress...) is invoked. Use onProgressUpdate() to report any form of progress to the UI thread while the background computation is executing. For instance, you can use it to pass the data to animate a progress bar or show logs in a text field.
  4. onPostExecute(Result) runs on the UI thread after the background computation has finished. The result of the background computation is passed to this method as a parameter.

For complete details on these methods, see the AsyncTask reference. Below is a diagram of their calling order.  Diagram for method calls of an AsyncTask, noborder

AsyncTask usage

To use the AsyncTask class, define a subclass of AsyncTask that overrides the doInBackground(Params...) method (and usually the onPostExecute(Result) method as well). This section describes the parameters and usage of AsyncTask, then shows a complete example.

AsyncTask parameters

In your subclass of AsyncTask, provide the data types for three kinds of parameters:

  • "Params" specifies the type of parameters passed to doInBackground() as an array.
  • "Progress" specifies the type of parameters passed to publishProgress() on the background thread. These parameters are then passed to the onProgressUpdate() method on the main thread.
  • "Result" specifies the type of parameter that doInBackground() returns. This parameter is automatically passed to onPostExecute() on the main thread.

Specify a data type for each of these parameter types, or use Void if the parameter type will not be used. For example:

public class MyAsyncTask extends AsyncTask <String, Void, Bitmap>{}

In this class declaration:

  • The "Params" parameter type is String, which means that MyAsyncTask takes one or more strings as parameters in doInBackground(), for example to use in a query.
  • The "Progress" parameter type is Void, which means that MyAsyncTask won't use the publishProgress() or onProgressUpdate() methods.
  • The "Result" parameter type is Bitmap. MyAsyncTask returns a Bitmap in doInbackground(), which is passed into onPostExecute().

Example of an AsyncTask

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

The example above goes through three of the four basic AsyncTask steps:

  • doInBackground() downloads content, a long-running task. It computes the percentage of files downloaded from the index of the for loop and passes it to publishProgress(). The check for isCancelled() inside the for loop ensures that if the task has been cancelled, the system does not wait for the loop to complete.
  • onProgressUpdate() updates the percent progress. It is called every time the publishProgress() method is called inside doInBackground(), which updates the percent progress.
  • doInBackground() computes the total number of bytes downloaded and returns it. onPostExecute() receives the returned result and passes it into onPostExecute(), where it is displayed in a dialog.

The parameter types used in this example are:

  • URL for the "Params" parameter type. The URL type means you can pass any number of URLs into the call, and the URLs are automatically passed into the doInBackground() method as an array.
  • Integer for the "Progress" parameter type.
  • Long for the "Result" parameter type.

Executing an AsyncTask

After you define a subclass of AsyncTask, instantiate it on the UI thread. Then call execute() on the instance, passing in any number of parameters. (These parameters correspond to the "Params" parameter type discussed above).

For example, to execute the DownloadFilesTask task defined above, use the following line of code:

new DownloadFilesTask().execute(url1, url2, url3);

Cancelling an AsyncTask

You can cancel a task at any time, from any thread, by invoking the cancel() method.

  • The cancel() method returns false if the task could not be cancelled, typically because it has already completed normally. Otherwise, cancel() returns true.
  • To find out whether a task has been cancelled, check the return value of isCancelled() periodically from doInBackground(Object[]), for example from inside a loop as shown in the example below. The isCancelled() method returns true if the task was cancelled before it completed normally.
  • After an AsyncTask task is cancelled, onPostExecute() will not be invoked after doInBackground() returns. Instead, onCancelled(Object) is invoked. The default implementation of onCancelled(Object) calls onCancelled() and ignores the result.
  • By default, in-process tasks are allowed to complete. To allow cancel() to interrupt the thread that's executing the task, pass true for the value of mayInterruptIfRunning.

Limitations of AsyncTask

AsyncTask is impractical for some use cases:

  • Changes to device configuration cause problems.

    When device configuration changes while an AsyncTask is running, for example if the user changes the screen orientation, the activity that created the AsyncTask is destroyed and re-created. The AsyncTask is unable to access the newly created activity, and the results of the AsyncTask aren't published.

  • Old AsyncTask objects stay around, and your app may run out of memory or crash.

    If the activity that created the AsyncTask is destroyed, the AsyncTask is not destroyed along with it. For example, if your user exits the app after the AsyncTask has started, the AsyncTask keeps using resources unless you call cancel().

When to use AsyncTask:

  • Short or interruptible tasks.
  • Tasks that don't need to report back to UI or user.
  • Low-priority tasks that can be left unfinished.

For all other situations, use AsyncTaskLoader, which is part of the Loader framework described next.

Loaders

Background tasks are commonly used to load data such as forecast reports or movie reviews. Loading data can be memory intensive, and you want the data to be available even if the device configuration changes. For these situations, use loaders, which are classes that facilitate loading data into an activity.

Loaders use the LoaderManager class to manage one or more loaders. LoaderManager includes a set of callbacks for when the loader is created, when it's done loading data, and when it's reset.

Starting a loader

Use the LoaderManager class to manage one or more Loader instances within an activity or fragment. Use initLoader() to initialize a loader and make it active. Typically, you do this within the activity's onCreate() method. For example:

// Prepare the loader. Either reconnect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

If you're using the Support Library, make this call using getSupportLoaderManager() instead of getLoaderManager(). For example:

getSupportLoaderManager().initLoader(0, null, this);

The initLoader() method takes three parameters:

  • A unique ID that identifies the loader. This ID can be whatever you want.
  • Optional arguments to supply to the loader at construction, in the form of a Bundle. If a loader already exists, this parameter is ignored.
  • A 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 has two possible outcomes:

  • If the loader specified by the ID already exists, the last loader created using that ID is reused.
  • If the loader specified by the ID doesn't exist, initLoader() triggers the onCreateLoader() method. This is where you implement the code to instantiate and return a new loader.
Note: Whether initLoader() creates a new loader or reuses an existing one, the given LoaderCallbacks implementation is associated with the loader and is called when the loader's state changes. If the requested loader exists and has already generated data, then the system calls onLoadFinished() immediately (during initLoader()), so be prepared for this to happen. Put the call to initLoader() in onCreate() so that the activity can reconnect to the same loader when the configuration changes. That way, the loader doesn't lose the data it has already loaded.

Restarting a loader

When initLoader() reuses an existing loader, it doesn't replace the data that the loader contains, but sometimes you want it to. For example, when you use a user query to perform a search and the user enters a new query, you want to reload the data using the new search term. In this situation, use the restartLoader() method and pass in the ID of the loader you want to restart. This forces another data load with new input data.

About the restartLoader() method:

  • restartLoader() uses the same arguments as initLoader().
  • restartLoader() triggers the onCreateLoader() method, just as initLoader() does when creating a new loader.
  • If a loader with the given ID exists, restartLoader() restarts the identified loader and replaces its data.
  • If no loader with the given ID exists, restartLoader() starts a new loader.

LoaderManager callbacks

The LoaderManager object automatically calls onStartLoading() when creating the loader. After that, the LoaderManager manages the state of the loader based on the state of the activity and data, for example by calling onLoadFinished() when the data has loaded.

To interact with the loader, use one of the LoaderManager callbacks in the activity where the data is needed:

  • Call onCreateLoader() to instantiate and return a new loader for the given ID.
  • Call onLoadFinished() when a previously created loader has finished loading. This is typically the point at which you move the data into activity views.
  • Call onLoaderReset() when a previously created loader is being reset, which makes its data unavailable. At this point your app should remove any references it has to the loader's data.

The subclass of the Loader is responsible for actually loading the data. Which Loader subclass you use depends on the type of data you are loading, but one of the most straightforward is AsyncTaskLoader, described next. AsyncTaskLoader uses an AsyncTask to perform tasks on a worker thread.

AsyncTaskLoader

AsyncTaskLoader is the loader equivalent of AsyncTask. AsyncTaskLoader provides a method, loadInBackground(), that runs on a separate thread. The results of loadInBackground() are automatically delivered to the UI thread, by way of the onLoadFinished() LoaderManager callback.

AsyncTaskLoader usage

To define a subclass of AsyncTaskLoader, create a class that extends AsyncTaskLoader<D>, where D is the data type of the data you are loading. For example, the following AsyncTaskLoader loads a list of strings:

public static class StringListLoader extends AsyncTaskLoader<List<String>> {}

Next, implement a constructor that matches the superclass implementation:

  • Your constructor takes the application context as an argument and passes it into a call to super().
  • If your loader needs additional information to perform the load, your constructor can take additional arguments.

In the example shown below, the constructor takes a query term.

public StringListLoader(Context context, String queryString) {
   super(context);
   mQueryString = queryString;
}

To perform the load, use the loadInBackground() override method, the corollary to the doInBackground() method of AsyncTask. For example:

@Override
public List<String> loadInBackground() {
    List<String> data = new ArrayList<String>;
    //TODO: Load the data from the network or from a database
    return data;
}

Implementing the callbacks

Use the constructor in the onCreateLoader() LoaderManager callback, which is where the new loader is created. For example, this onCreateLoader() callback uses the StringListLoader constructor defined above:

@Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
   return new StringListLoader(this, args.getString("queryString"));
}

The results of loadInBackground() are automatically passed into the onLoadFinished() callback, which is where you can display the results in the UI. For example:

public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
    mAdapter.setData(data);
}

The onLoaderReset() callback is only called when the loader is being destroyed, so you can leave onLoaderReset() blank most of the time, because you won't try to access the data after the loader is destroyed.

When you use AsyncTaskLoader, your data survives device-configuration changes. If your activity is permanently destroyed, the loader is destroyed with it, with no lingering tasks that consume system resources.

Loaders have other benefits too, for example, they let you monitor data sources for changes and reload the data if a change occurs. You learn more about the specifics of loaders in a future lesson.

The related practical is in 7.1: AsyncTask.

Learn more

results matching ""

    No results matching ""