This practical codelab is part of Unit 3: Working in the background in the Android Developer Fundamentals (Version 2) course. You will get the most value out of this course if you work through the codelabs in sequence:

Introduction

A thread is an independent path of execution in a running program. When an Android program is launched, the system creates a main thread, which is also called the UI thread. This UI thread is how your app interacts with components from the Android UI toolkit.

Sometimes an app needs to perform resource-intensive tasks such as downloading files, making database queries, playing media, or computing complex analytics. This type of intensive work can block the UI thread so that the app doesn't respond to user input or draw on the screen. Users may get frustrated and uninstall your app.

To keep the user experience (UX) running smoothly, the Android framework provides a helper class called AsyncTask, which processes work off of the UI thread. Using AsyncTask to move intensive processing onto a separate thread means that the UI thread can stay responsive.

Because the separate thread is not synchronized with the calling thread, it's 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 learn how to add a background task to your Android app using an AsyncTask.

What you should already know

You should be able to:

What you'll learn

What you'll do

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 looks like:

The SimpleAsyncTask UI contains a Button that launches the AsyncTask, and a TextView that displays the status of the app.

1.1 Create the project and layout

  1. Create a new project called SimpleAsyncTask using the Empty Activity template. Accept the defaults for all other options.
  2. Open the activity_main.xml layout file. Click the Text tab.
  3. Add the layout_margin attribute to the top-level ConstraintLayout:
android:layout_margin="16dp"
  1. Add or modify the following attributes of the "Hello World!" TextView to have these values. Extract the string into a resource.

Attribute

Value

android:id

"@+id/textView1

android:text

"I am ready to start work!"

android:textSize

"24sp"

  1. Delete the app:layout_constraintRight_toRightOf and app:layout_constraintTop_toTopOf attributes.
  2. Add a Button element just under the TextView, and give it these attributes. Extract the button text into a string resource.

Attribute

Value

android:id

"@+id/button"

android:layout_width

"wrap_content"

android:layout_height

"wrap_content"

android:text

"Start Task"

android:layout_marginTop

"24dp"

android:onClick

"startTask"

app:layout_constraintStart_toStartOf

"parent"

app:layout_constraintTop_toBottomOf

"@+id/textView1"

  1. The onClick attribute for the button will be highlighted in yellow, because the startTask() method is not yet implemented in 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.

Solution code for activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_margin="16dp"
   tools:context=".MainActivity">

   <TextView
       android:id="@+id/textView1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/ready_to_start"
       android:textSize="24sp"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  
   <Button
       android:id="@+id/button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="24dp"
       android:onClick="startTask"
       android:text="@string/start_task"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/textView1"/>

</android.support.constraint.ConstraintLayout>

AsyncTask is an abstract class, which means you must subclass it in order to use it. In this example the AsyncTask performs a very simple background task: it 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 to beat the current Go champion.

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

When you create an AsyncTask subclass, 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. When you create an AsyncTask subclass, you can configure it using these parameters:

For example, an AsyncTask subclass called MyAsyncTask with the following class declaration might take the following parameters:

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

In this task you will use an AsyncTask subclass to define work that will run in a different thread than the UI thread.

2.1 Subclass the AsyncTask

In this app, the AsyncTask subclass you create 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.

Use Void for the params, because this AsyncTask does not require any inputs. Use Void for the progress type, because the progress is not published. Use a String as the result type, because you will update the TextView with a string when the AsyncTask has completed execution.

public class SimpleAsyncTask extends AsyncTask <Void, Void, String>{}
  1. At the top of the class, define a member variable mTextView of the type WeakReference<TextView>:
private WeakReference<TextView> mTextView;
  1. Implement a constructor for AsyncTask that takes a TextView as a parameter and creates a new weak reference for that TextView:
SimpleAsyncTask(TextView tv) {
       mTextView = new WeakReference<>(tv);
}

The AsyncTask needs to update the TextView in the Activity once it has completed sleeping (in the onPostExecute() method). The constructor for the class will therefore need a reference to the TextView to be updated.

What is the weak reference (the WeakReference class) for? If you pass a TextView into the AsyncTask constructor and then store it in a member variable, that reference to the TextView means the Activity cannot ever be garbage collected and thus leaks memory, even if the Activity is destroyed and recreated as in a device configuration change. This is called creating a leaky context, and Android Studio will warn you if you try it.

The weak reference prevents the memory leak by allowing the object held by that reference to be garbage collected if necessary.

2.2 Implement doInBackground()

The doInBackground() method is required for your AsyncTask subclass.

  1. 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. The following method template is added to your class:
@Override
protected String doInBackground(Void... voids) {
   return null;
}
  1. Add code to generate a random integer between 0 and 10. This is the number of milliseconds the task will pause. This is not a lot of time to pause, so multiply that number by 200 to extend that time.
Random r = new Random();
int n = r.nextInt(11);

int s = n * 200;
  1. Add a try/catch block and put the thread to sleep.
try {
   Thread.sleep(s);
} catch (InterruptedException e) {
   e.printStackTrace();
}
  1. Replace the existing return statement to return the String "Awake at last after sleeping for xx milliseconds", where xx is the number of milliseconds the app slept.
return "Awake at last after sleeping for " + s + " milliseconds!";

The complete doInBackground() method looks like this:

@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 and display that string in the TextView:
protected void onPostExecute(String result) {
   mTextView.get().setText(result);
}

The String parameter to this method is what you defined in the third parameter of your AsyncTask class definition, and what your doInBackground() method returns.

Because mTextView is a weak reference, you have to deference it with the get() method to get the underlying TextView object, and to call setText() on it.

3.1 Implement the method that starts the AsyncTask

Your app now has an AsyncTask class that performs work in the background (or it would if it didn't call sleep() as the simulated work). You can now implement the onClick method for the "Start Task" button to trigger the background task.

  1. In the MainActivity.java file, add a member variable to store the TextView.
private TextView mTextView;
  1. In the onCreate() method, initialize mTextView to the TextView in the layout.
mTextView = findViewById(R.id.textView1);
  1. In the startTask() method, Update the TextView to show the text "Napping...". Extract that message into a string resource.
mTextView.setText(R.string.napping);
  1. Create an instance of SimpleAsyncTask, passing the TextView mTextView to the constructor. Call execute() on that SimpleAsyncTask instance.
new SimpleAsyncTask(mTextView).execute();

Solution code for MainActivity:

package com.example.android.simpleasynctask;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
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 {

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

  @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mTextView = findViewById(R.id.textView1);
   }

   public void startTask(View view) {
       // Put a message in the text view
       mTextView.setText(R.string.napping);

       // Start the AsyncTask.
       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.

There are several things going on here:

For these reasons, an AsyncTask is 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 background class called an AsyncTaskLoader which you will learn about in a later practical.

In order to prevent the TextView from resetting to the initial string, you need to save its state. You will now implement onSaveInstanceState() to preserve the content of your TextView when the activity is destroyed in response to a configuration change such as device rotation.

  1. At the top of the class, add a constant for the key for the current text in the state bundle:
private static final String TEXT_STATE = "currentText";
  1. Override the onSaveInstanceState() method in MainActivity to preserve the text inside the TextView when the activity is destroyed:
@Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Save the state of the TextView
        outState.putString(TEXT_STATE, 
            mTextView.getText().toString());
    }
}
  1. In onCreate(), retrieve the value of the TextView from the state bundle when the activity is restored.
// 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 text view.
        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());
    }
}

Android Studio project: SimpleAsyncTask

Challenge: The AsyncTask class provides another 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.

The related concept documentation is in 7.1: AsyncTask and AsyncTaskLoader.

Android developer documentation:

Other resources:

Videos:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Open the SimpleAsyncTask app. Add a ProgressBar that displays the percentage of sleep time completed. The progress bar fills up as the AsyncTask thread sleeps from a value of 0 to 100 (percent).

Hint: Break up the sleep time into chunks.

For help, see the AsyncTask reference.

Answer these questions

Question 1

For a ProgressBar:

  1. How do you determine the range of values that a ProgressBar can show?
  2. How do you change how much of the progress bar is filled in?

Question 2

If an AsyncTask is defined as follows:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long>
  1. What is the type of the value that is passed to doInBackground() in the AsyncTask?
  2. What is the type of the value that is passed to the callback that reports the progress of the task?
  3. What is the type of the value that is passed to the callback that is executed when the task completes?

Question 3

To report progress of the work executed by an AsyncTask, what callback method do you implement, and what method do you call in your AsyncTask subclass?

Submit your app for grading

Guidance for graders

Check that the app has the following features:

To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).

For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).