Background Tasks - AsyncTask

Intro

As discussed in the Threads lesson, if you want to run a task in the background without blocking the User Interface (UI) thread, you must do that work in another thread.  You have already been introduced to a couple ways to accomplish that.  But there are downsides to those techniques which can cause massive developer headaches.  This lesson will introduce a special abstract class provided to us by Android known as an AsyncTask, built specifically to address the problems you may encounter when writing your own threading code.

Why not just use Threads?

Android is written in Java, which means that we can use the standard Java Threads class for asynchronous task execution.  In fact, you've already seen this in a previous lesson:

Thread thread = new Thread(){
    public void run( ) { 
        try { 
            Thread.sleep(5000); //pause for 5 seconds 
        } catch (InterruptedException e) { 
            e.printStackTrace();
        ​} 
    } 
};
thread.start();

In this simple example, using a Thread worked just fine.  However, in less trivial applications, there are a few disadvantages to using threads that start to manifest themselves:

Handlers provide as solution for updating the UI thread from another thread, but they don't provide a solution for any of the other issues discussed above.  To do so, you would need to generate your own code.   Sure, you can take the time to write all of this if you really want to, but you don't have to!  Chances are, you can use an AsyncTask instead.  

AsyncTask

An AsyncTask is a Thread and Handler helper class designed specifically for running background tasks.  It has built-in mechanisms for interacting with the UI thread.  Because it is an abstract class, It must be sub-classed.  AsyncTask provides a means of interacting with the UI at three phases of execution: before, during and after the background task runs. This is accomplished at each stage of task execution.

AsyncTask goes through these four steps on execution (courtesy of Google):

  1. onPreExecute(), invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
  2. doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
  3. onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
  4. onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.

To see AsyncTask in action, modify your the sample app you created during the Threads lesson.  Add the following AsyncTask as an inner class of the MainActivity:

public class MyAsyncTask extends AsyncTask<String, Integer, Long> {
    
    @Override
    protected void onPreExecute(){
        // do work in UI thread before doInBackground() runs
        outputText.setText("Starting Task...");
    }


    @Override
    protected Long doInBackground(String... params) {
        // do work in background thread

        Long someValue = 1775L;

        try {
            Thread.sleep(5000); // simulate work  
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return someValue;
    }

    @Override
    protected void onPostExecute(Long result) {
        // do work in UI thread after doInBackground() completes
        outputText.setText("Task Result: " + result);
    }

}

In your activity's runTask() method, comment out all code for Thread and/or TimerTask.  AsyncTasks are ran by calling the execute() method.  Add this to the runTask() method:

new MyAsyncTask().execute();

Run your app.  If you did everything correctly, you should see the TextView update after 5 seconds.  Of course, in a real app, more significant background work is usually accomplished in the doInBackground() method.

Publishing Progress

To communicate with the UI thread while the background task is running, you use the publishProgress method.  This method is called from doInBackground, and triggers the onProgressUpdate method.  The onProgressUpdate method runs on the UI thread, so it can be used to update the UI.  Let's modify our app so that the TextView shows the progress as the background task is running:

@Override
protected Long doInBackground(String... params) {
    // do work in background thread

    Long someValue = 1775L;

    int progress = 0;

    while(progress < 100){
        try {
            Thread.sleep(100); //pause for 100 milliseconds
            
            // push updates to UI thread
            publishProgress(progress);
            progress++;
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    return someValue;
}

@Override
protected void onProgressUpdate(Integer... progress) {
    // update UI thread according to progress,
    // e.g. update a ProgressBar display
    outputText.setText("Progress: " + progress[0]);
}

Cancelling a Task

In addition to providing a more streamlined mechanism for updating the UI thread, AsyncTasks also have the built in ability to cancel a task by calling the cancel() method.  And what happens when you call cancel?  The background task can still continue to run!  the cancel method's boolean argument tells the task whether it should try to interrupt the task or not.  Additionally, the AsyncTask class provides a way to check whether a task has been canceled.  If you are able to check during the execution of a background task (e.g. your task runs in a loop), you can choose to end the task at that point, or let it finish.  If the cancel method is called, the AsyncTask does not execute the onPostExecute method.  Instead, it executes the onCancelled method.

Let's implement the cancel functionality in our app:

@Override
protected Long doInBackground(String... params) {
    // do work in background thread

    Long someValue = 1775L;

    int progress = 0;

    while(progress < 100){
        try {
            Thread.sleep(100); //pause for 100 milliseconds
            
            // push updates to UI thread
            publishProgress(progress);

            // check to see if the task has been canceled
            // using taskName.cancel(bool) from UI thread
            // cancel(true) = task is stopped before completion
            // cancel(false) = task is allowed to complete
            if(isCancelled()){
                // handle call to cancel
                progress = 100;
            }
            
            progress++;
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    return someValue;
}

@Override
protected void onCancelled (Long result){
    // executed on UI thread
    // called instead of onPostExecute() after doInBackground() 
    // is complete if cancel() is called on the AsyncTask
    outputText.setText("Task Cancelled");
}

Parameter Mapping

Keeping track of which parameters go where, and which values get passed to which methods in an AsyncTask can get confusing.  When you define your AsyncTask, you define the Object types that are returned by each method.  In the example above, we used AsyncTask<String, Integer, Long>, but we could have used other types had our task needed it.  What's important is that you understand the flow of variables.  The easiest way to explain is with a picture:

Final Thoughts

Of course, AsyncTask isn't a panacea to all of our threading woes; there are a couple things to keep in mind:

Task: add a button to your app.  When the button is pressed, a new random number is generated on another thread and displayed in the TextView