7.1: Create an AsyncTask

Contents:

A thread is an independent path of execution in a running program. When an Android program is launched, the Android Runtime system creates a thread called the "Main" thread. As your program runs, each line of code is executed in a serial fashion, line by line. This main thread is how your application interacts with components from the Android UI Toolkit, and it's why the main thread is sometimes called the "UI thread". However, sometimes an application needs to perform resource-intensive work, such as downloading files, database queries, playing media, or computing complex analytics. This type of intensive work can block the UI thread if all of the code executes serially on a single thread. When the app is performing resources intensive work, the app does not respond to the user or draw on the screen because it is waiting for that work to be done. This can yield poor performance, which negatively affects the user experience. Users may get frustrated and uninstall your Android app if the performance of the app is slow.

To keep the user experience (UX) running smoothly and responding quickly to user gestures, the Android Framework provides a helper class called AsyncTask which processes work off of the UI thread. An AsyncTask is an abstract Java class that provides one way to move this intensive processing onto a separate thread, thereby allowing the UI thread to remain very responsive. Since the separate thread is not synchronized with the calling thread, it is called an asynchronous thread. An AsyncTask also contains a callback that allows you to display the results of the computation back in the UI thread.

In this practical, you will learn how to add a background task to your Android app using an AsyncTask.

What you should already KNOW

You should be able to:

  • Create an Activity.
  • Add a TextView to the layout for the activity.
  • Programmatically get the id for the TextView and set its content.
  • Use Button views and their onClick functionality.

What you will LEARN

During this practical, you will learn to:

  • Add an AsyncTask to your app in order to run a task in the background, off of the UI thread.
  • Identify and understand the benefits and drawbacks of using AsyncTask for background tasks.

What you will DO

During this practical, you will:

  • Create a simple application that executes a background task using an AsyncTask.
  • Run the app and see what happens when you rotate the screen.

App overview

You will build an app that has one TextView and one button. When the user clicks the button, the app sleeps for a random amount of time, and then displays a message in the TextView when it wakes up.

Here's what the finished app will look like: Preview for the SimpleAsyncTask app

Task 1. Set up the SimpleAsyncTask project

The SimpleAsyncTask UI is straightforward. It contains a button that launches the AsyncTask, and a TextView that displays the status of the application.

1.1 Create the layout

  1. Create a new project called SimpleAsyncTask using the Empty Activity template. (Accept the defaults for the other options.)
  2. Open the activity_main.xml layout file.

    1. Change the root view to LinearLayout.
    2. In the "Hello World" TextView element, remove the layout_constraint attributes, if they are present.
    3. Add the following essential UI elements to the layout:
      View Attributes Values
      LinearLayout android:orientation vertical
      TextView android:text

      android:id

      I am ready to start work!

      @+id/textView1

      Button android:text

      android:onClick

      Start Task

      startTask

      Note: You can set the layout height and width of each view to whatever you want, as long the views remain on the screen independent of the screen size (using wrap_content ensures that this is the case).
  3. The onClick attribute for the button will be highlighted in yellow, since the startTask() method is not yet implemented in the MainActivity. Place your cursor in the highlighted text, press Alt + Enter (Option + Enter on a Mac) and choose Create 'startTask(View) in 'MainActivity' to create the method stub in MainActivity.

Depending on your version of Android Studio, the activity_main.xml layout file will look something like the following:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:paddingLeft="@dimen/activity_horizontal_margin"
       android:paddingRight="@dimen/activity_horizontal_margin"
       android:paddingTop="@dimen/activity_vertical_margin"
       android:paddingBottom="@dimen/activity_vertical_margin"
       android:orientation="vertical">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/ready_to_start"
           android:id = "@+id/textView1"
           android:textSize="24sp"/>

       <Button
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/start_task"
           android:id="@+id/button"
           android:layout_marginTop="56dp"
           android:onClick="startTask" />
    </LinearLayout>

Task 2. Create the AsyncTask subclass

Since AsyncTask is an abstract class, you need to subclass it in order to use it. In this example the AsyncTask will execute a very simple background task: it just sleeps for a random amount of time. In a real app, the background task could perform all sorts of work, from querying a database, to connecting to the Internet, to calculating the next Go move so that you can beat the current Go champion.

An AsyncTask has the following methods for performing work off of the main thread:

  • onPreExecute(): This method runs on the UI thread, and is used for setting up your task (like showing a progress bar).
  • doInBackground(): This is where you implement the code to execute the work that is to be performed on the separate thread.
  • onProgressUpdate(): This is invoked on the UI thread and used for updating progress in the UI (such as filling up a progress bar)
  • onPostExecute(): Again on the UI thread, this is used for updating the results to the UI once the AsyncTask has finished loading. Diagram for the Threading of an AsyncTask
    Note: A background or worker thread is any thread which is not the main or UI thread.

When you create an AsyncTask, you may need to give it information about the work which it is to perform, whether and how to report its progress, and in what form to return the result.

In this exercise you will use an AsyncTask subclass to define work that will run in a different thread than the UI thread, which will avoid any performance issues.

When you create an AsyncTask, you can configure it using these parameters:

  • Params: The data type of the parameters sent to the task upon executing the doInBackground() override method.
  • Progress: The data type of the progress units published using the onProgressUpdated() override method.
  • Result: The data type of the result delivered by the onPostExecute() override method.

For example, an AsyncTask with the following class declaration would take a String as a parameter in doInBackground() (to use in a query, for example), an Integer for onProgressUpdate() (percentage of job complete), and a Bitmap for the the result in onPostExecute() (the query result):

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

2.1 Subclass the AsyncTask

In your first AsyncTask implementation, the AsyncTask subclass will be very simple. It does not require a query parameter or publish its progress. You will only be using the doInBackground() and onPostExecute() methods.

  1. Create a new Java class called SimpleAsyncTask that extends AsyncTask and takes three generic type parameters:
    • Void for the params, since this AsyncTask does not require any inputs.
    • Void for the progress type, since the progress is not published.
    • A String as the result type, since you will update the TextView with a string when the AsyncTask has completed execution.
      public class SimpleAsyncTask extends AsyncTask <Void, Void, String>{}
      
      Note: The class declaration will be underlined in red, since the required method doInBackground() has not yet been implemented.
      The AsyncTask will need to update the TextView once it has completed sleeping. The constructor will then need to include the TextView, so that it can be updated in onPostExecute().
  2. Define a member variable mTextView.
  3. Implement a constructor for AsyncTask that takes a TextView and sets mTextView to the one passed in TextView:
     public SimpleAsyncTask(TextView tv) {
        mTextView = tv;
     }
    

2.2 Implement doInBackground()

  1. Add the required doInBackground() method. Place your cursor on the highlighted class declaration, press Alt + Enter (Option + Enter on a Mac) and select Implement methods. Choose doInBackground() and click OK:
    @Override
    protected String doInBackground(Void... voids) {
       return null;
    }
    
  2. Implement doInBackground() to:

    • Generate a random integer between 0 and 10
    • Multiply that number by 200
    • Put the current thread to sleep. (Use Thread.sleep()) in a try/catch block.
    • Return the String "Awake at last after xx milliseconds" (where xx is the number of milliseconds the app slept)

      @Override
      protected String doInBackground(Void... voids) {
      
         // Generate a random number between 0 and 10
         Random r = new Random();
         int n = r.nextInt(11);
      
         // Make the task take long enough that we have
         // time to rotate the phone while it is running
         int s = n * 200;
      
         // Sleep for the random amount of time
         try {
             Thread.sleep(s);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
      
         // Return a String result
         return "Awake at last after sleeping for " + s + " milliseconds!";
      }
      

2.3 Implement onPostExecute()

When the doInBackground() method completes, the return value is automatically passed to the onPostExecute() callback.

  1. Implement onPostExecute() to take a String argument (this is what you defined in the third parameter of AsyncTask and what your doInBackground() method returned) and display that string in the TextView:
    protected void onPostExecute(String result) {
       mTextView.setText(result);
    }
    
    Note: You can update the UI in onPostExecute() because it is run on the main (UI) thread. You cannot call mTextView.setText() in doInBackground(), because that method is executed on a separate thread.

Task 3. Implement the final steps

3.1 Implement the method that starts the AsyncTask

Your app now has an AsyncTask that performs work in the background (or it would if you didn't call sleep() as the simulated work.) You can now implement the method that gets called when the Start Task button is clicked, to trigger the background task.

  1. In the MainActivity.java file, add a member variable to store the TextView.
    private TextView mTextView;
    
  2. In the onCreate() method, initialize mTextView to the TextView in the UI.
  3. Add code to the startTask() method to create an instance of SimpleAsyncTask, passing the TextView mTextView to the constructor.
  4. Call execute() on that SimpleAsyncTask instance.

    Note: The execute() method is where you pass in the parameters (separated by commas) that are then passed into doInBackground() by the system. Since this AsyncTask has no parameters, you will leave it blank.
  5. Update the TextView to show the text "Napping…"

    public void startTask (View view) {
       // Put a message in the text view
       mTextView.setText("Napping... ");
    
       // Start the AsyncTask.
       // The AsyncTask has a callback that will update the text view.
       new SimpleAsyncTask(mTextView).execute();
    }
    

Solution code for MainActivity:

package android.example.com.simpleasynctask;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

   // The TextView where we will show results
   TextView mTextView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       //  Initialize mTextView
       mTextView = (TextView) findViewById(R.id.textView1);

   }

   public void startTask (View view) {
       // Put a message in the text view
       mTextView.setText("Napping... ");

       // Start the AsyncTask.
       // The AsyncTask has a callback that will update the text view.
       new SimpleAsyncTask(mTextView).execute();
   }
}

3.2 Implement onSaveInstanceState()

  1. Run the app and click the Start Task button. How long does the app nap?
  2. Click the Start Task button again, and while the app is napping, rotate the device. If the background task completes before you can rotate the phone, try again. Alternatively, you can update the code and make it sleep for a longer time period.

    Note: You'll notice that when the device is rotated, the TextView resets to its initial content and the AsyncTask doesn't seem able to update the TextView.

    There are several things going on here:

    • When you rotate the device, the system restarts the app, calling onDestroy() and then onCreate(), which restarts the activity lifecycle. Since the AsyncTasks are no longer connected to the lifecycle of your app, and cannot reconnect to the activity.
    • The AsyncTasks will continue running to completion in the background, consuming system resources, but never showing the results in the UI, which gets reset in onCreate(). It will never be able to update the TextView that was passed to it, since that particular TextView has also been destroyed. Eventually, the system run out of resources, and will fail.
    • Even without the AsyncTask, the rotation of the device resets all of the UI elements to their default state, which for the TextView implies a particular string that you set in the activity_main.xml file.

    For these reasons, AsyncTasks are not well suited to tasks which may be interrupted by the destruction of the Activity. In use cases where this is critical you can use a different type of class called a Loader, which you will implement in a later practical.

    In order to prevent the TextView from resetting to the initial string, you need to save its state. You've already learned how to maintain the state of views in a previous practical, using the SavedInstanceState class.

    You will now implement onSaveInstanceState() to preserve the content of your TextView when the activity is spontaneously destroyed.

    Note: Not all uses of AsyncTask require you to handle the state of the views on rotation. This app uses a TextView to display the results of the app, so preserving the state is useful. In other cases, such as uploading a file, you may not need any persistent information in the UI, so retaining the state is not critical.
  3. Override the onSaveInstanceState() method in MainActivity to preserve the text inside the TextView when the activity is destroyed:

    outState.putString(TEXT_STATE, mTextView.getText().toString());
    
  4. Retrieve the value of the TextView when the activity is restored in the onCreate() method.
    // Restore TextView if there is a savedInstanceState
    if(savedInstanceState!=null){
      mTextView.setText(savedInstanceState.getString(TEXT_STATE));
    }
    

Solution code for MainActivity:

package android.example.com.simpleasynctask;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

/**
 * The SimpleAsyncTask app contains a button that launches an AsyncTask
 * which sleeps in the asynchronous thread for a random amount of time.
 */
public class MainActivity extends AppCompatActivity {

    //Key for saving the state of the TextView
    private static final String TEXT_STATE = "currentText";

    // The TextView where we will show results
    private TextView mTextView = null;

    /**
     * Initializes the activity.
     * @param savedInstanceState The current state data
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //  Initialize mTextView
        mTextView = (TextView) findViewById(R.id.textView1);

        // Restore TextView if there is a savedInstanceState
        if(savedInstanceState!=null){
           mTextView.setText(savedInstanceState.getString(TEXT_STATE));
        }
    }

    /**`
     * Handles the onCLick for the "Start Task" button. Launches the AsyncTask
     * which performs work off of the UI thread.
     *
     * @param view The view (Button) that was clicked.
     */
    public void startTask (View view) {
        // Put a message in the text view
        mTextView.setText(R.string.napping);

        // Start the AsyncTask.
        // The AsyncTask has a callback that will update the textview.
        new SimpleAsyncTask(mTextView).execute();
    }

    /**
     * Saves the contents of the TextView to restore on configuration change.
     * @param outState The bundle in which the state of the activity is saved       when it is spontaneously destroyed.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Save the state of the TextView
        outState.putString(TEXT_STATE, mTextView.getText().toString());
    }
}

Solution code

Android Studio project: SimpleAsyncTask

Coding challenge

Note: All coding challenges are optional and are not prerequisites for later lessons.

Challenge: AsyncTask provides another very useful override method: onProgressUpdate(), which allows you to update the UI while the AsyncTask is running. Use this method to update the UI with the current sleep time. Look to the AsyncTask documentation to see how onProgressUpdate() is properly implemented. Remember that in the class definition of your AsyncTask, you will need to specify the data type to be used in the onProgressUpdate() method.

Summary

  • Avoid resource-intensive work in the UI thread which may make your UI sluggish or erratic.
    • Any code that does not involve drawing the UI or responding to the user input should be moved from the UI thread to another, separate thread.
  • An AsyncTask is an abstract Java class that moves intensive processing onto a separate thread.
    • AsyncTask must be subclassed to be used.
    • AsyncTask has 4 useful methods: onPreExecute(), doInBackground(), onPostExecute() and onProgressUpdate().
  • doInBackground() is the only method that is run on a separate worker thread.
    • You should not call UI methods in your AsyncTask method.
    • The other methods of AsyncTask run in the UI thread and allow calling methods of UI components.
  • Rotating an Android device destroys and recreates an Activity. This can disassociate the UI from the background thread, which will continue to run.

The related concept documentation is in Android Developer Fundamentals: Concepts.

Learn more

Android developer documentation:

Other resources:

Videos:

results matching ""

    No results matching ""