APPENDIX D: The AsyncTask Class

Chapter opener image: © Fon_nongkran/Shutterstock

A Thread is a sequence of code that executes inside an existing process. The existing process is often called the Main Thread. For an Android app, it is also called the User Interface Thread. There can be several threads executing inside the same process, and threads can share resources, such as memory.

When we start the app, our code executes in the main thread. Sometimes, we need to start another thread. For example, we may need to retrieve data from a remote location. Furthermore, we may need that other thread to finish before we use and place its data inside a GUI component. The AsyncTask class, from the android.os package, allows us to perform a background operation and then access the main thread to communicate its results. It is designed to execute short tasks, lasting a few seconds or less, and should not be used to start a thread that runs for a long time. AsyncTask is abstract so we must subclass it and instantiate an object of the subclass in order to use it.

The AsyncTask class uses three generic types that we must specify when extending it. The syntax for the header of a subclass is as follows:

AccessModifier ClassName extends AsyncTask<Params, Progress, Result>

where Params, Progress, and Result are placeholders for actual class names and have the following meanings:

  • Params is the data type of the array that is passed to the execute method of AsyncTask when we call it.

  • Progress is the data type used for progress units as the task executes in the background. If we choose to report progress, that data type is often Integer or Long.

  • Result is the data type of the value returned upon execution of the task.

We may not be interested in using some of these types. If we are not interested in using one, we use Void. For example, let’s assume that the name of our class is TestTask and that we pass an array of Integers as the argument of the execute method. Let’s further assume that we do not care about reporting progress, and the task returns a String. Then, we use the following class header:

public TestTask extends AsyncTask<Integer, Void, String>

In order to launch a task using the AsyncTask class, we do the following:

  • ▸ Create a subclass of AsyncTask.

  • ▸ Declare and instantiate an object of that subclass.

  • ▸ With that object, call the execute method to start the task.

The execute method is public and final. AsyncTask includes some protected methods that can be overridden by a subclass. TABLE D.1 lists some of the methods of the AsyncTask class. As discussed previously, Params, Progress, and Result are placeholders for actual class names.

The three dots syntax after the data type in the parameter list indicates that a method accepts a variable number of arguments, also known as varargs. Varargs must be at the final parameter position in the parameter list. The syntax for a method header using a variable number of arguments is as follows:

AccessModifyer returnType methodName( someParameterList, DataType... variableNames )

TABLE D.1 Selected methods of the AsyncTask class

Method Description
AsyncTask execute( Params. . . params ) Public and final; params represent an array of values of type Params, a generic type; returns this instance of AsyncTask.
void onPreExecute( ) Protected. Automatically called after we call execute and before doInBackground starts.
Result doInBackground( Params. . . params ) Protected. Abstract method that we need to override to perform the task; params is the argument passed to execute when we call it.
void publishProgress( Progress. . . values ) Protected and final. Calling that method triggers a call to onProgressUpdate.
void onProgressUpdate( Progress. . . values ) Protected. Automatically called if we call publishProgress.
void onPostExecute( Result result ) Protected. Automatically called after doInBackground finishes; result is the value returned by doInBackground.

Note that there is no space between the data type and the three dots. The execute, doInBackground, publishProgress, and onProgressUpdate methods accept a variable number of arguments.

For example, and assuming we have defined a class named Item, we could override the doInBackground method using the following method header:

protected ArrayList<Item> doInBackground( Integer... numbers )

When calling a method that accepts a variable number of arguments, we can pass no argument, a single argument, several arguments, or an array of arguments. For example, to call a doInBackground method that has the method header defined above with an AsyncTask reference named task, we could do it four different ways:

// #1: pass no argument
task.execute( );
// #2: pass only 1 argument
task.execute( 7 );
// #3: pass several arguments
task.execute( 6, 8, 15 );
// #4: pass an array of arguments
int [ ] values = { 10, 20, 30, 40, 50 };
task.execute( values );

When we call the execute method, the following methods of the AsyncTask class execute in this order: onPreExecute, doInBackground, and onPostExecute.

Before the task executes, if we want to perform some initialization, we can place that code inside the onPreExecute method. We place the code for the task we want to execute in the doInBackground method. That method is abstract and must be overridden. The argument that is automatically passed to the doInBackground method is the same argument that we passed to the execute method when we call it. Inside doInBackground, as the task is executing, if we want to report progress to the main thread, we can call the publishProgress method. That in turns triggers a call to the onProgressUpdate method, which we can override: for example, we can update a progress bar in the user interface thread. When the doInBackground method finishes executing, the onPostExecute method is automatically called. We can override it to update the user interface with the results of the task that just completed. The argument that is automatically passed to the onPostExecute method is the value that is returned by the doInBackground method.

To summarize the flow of data, the argument that we pass to execute is automatically passed to doInBackground, and the returned value of doInBackground is automatically passed as the argument of onPostExecute.

Inside the onPostExecute method, it is very likely that we want to update the activity of the user interface thread. There are essentially two ways to do this:

  • ▸ We can code the subclass of AsyncTask as a private inner class of the activity class that executes the task. In this case, we can call methods of the activity class from inside the onPostExecute method,

  • ▸ We can code the subclass of AsyncTask as a separate public class. In this case, we need a reference to the activity class that executes the task so that we can call methods from that activity class from inside the onPostExecute method.

We show an example of the second solution (i.e., we code a separate public subclass of AsyncTask). It illustrates how to pass an Activity reference from one class to another. We perform the following steps:

  • ▸ We create TestTask, a subclass of AsyncTask.

  • ▸ We override the doInBackground method so that it performs the task we are interested in.

  • ▸ Inside TestTask, we include an instance variable that is a reference to the Activity that starts the task. This enables us to call methods of the Activity class from TestTask.

  • ▸ We override the onPostExecute method and call one or more methods of the Activity class to update the activity with the results of the task that just completed.

  • ▸ Inside the Activity class, we create and instantiate an object of TestTask.

  • ▸ We pass a reference to this Activity to the TestTask object.

  • ▸ We call the execute method of AsyncTask, passing whatever input data is appropriate (for example a URL if we need to retrieve data from a remote website), to start executing the task.

We now build a very simple app in order to illustrate how to use the AsyncTask class. We use the Empty Activity template. EXAMPLE D.1 shows the TestTask class. In the class header (line 6), we specify Integer as the Params data type, Void as the Progress data type (we are not interested in reporting progress here), and String as the Result data type.

EXAMPLE D.1 The TestTask class

At line 7, we declare the activity instance variable, a reference to MainActivity. The constructor (lines 9–12) accepts a MainActivity parameter and assigns it to activity. When we call the TestTask constructor from the MainActivity class, we pass this as the argument.

We override the doInBackground method at lines 14–17. The method header specifies that it returns a String (the Result data type) and that it accepts Integers as parameters (the Params data type). The parameters are never used in this simple example and therefore insignificant.

We override the onPostExecute method at lines 19–22. When it is called automatically after the doInBackground method finishes execution, the value of its parameter message is the String returned by doInBackground. We call the updateView method of the MainActivity class with the activity instance variable, passing message at line 21. The updateView method, which we need to add to the MainActivity class, is expected to update the View based on the value of its argument.

Inside all the methods, we add an output statement (lines 10, 15, 20) so that we can trace the order in which all the methods are called.

EXAMPLE D.2 shows the MainActivity class. At line 16, we declare and instantiate a TestTask object and call the execute method at line 18, passing 1. In this simple example, the argument we pass is never used and therefore irrelevant. However, its data type must match the data type of the parameters of the doInBackground method of the TestTask class. Since the execute method accepts a variable number of arguments, we can pass 0, 1, or more arguments. We could have passed an array of Integers containing only one element, 1, as follows:

task.execute( new Integer[ ] { 1 } );

EXAMPLE D.2 The MainActivity class

At lines 22–26, we code the updateView method. At this point, it outputs some feedback to Logcat and updates the text in the TextView at line 25.

Note that the sequencing of our code is important when we use an AsyncTask. After the call to execute at line 18, execution would continue inside the onCreate method if there were statements after line 18. In that case, there would be interleaved execution between those statements and the statements executing inside the TestTask class. If we attempted to retrieve the value or values generated by a task right after calling execute (even though the value[s] generated by the task is inside the TestTask class, it would be easy to write them as public static data of another class and read that public static data from the MainActivity class), in this example inside the onCreate method, that value or those values would very likely be null. The correct way to process the value generated by a task is to do it inside the onPostExecute method.

EXAMPLE D.3 shows the activity_main.xml file. We add an id for the TextView at line 16 and set the font size to 32 at line 17 so we can see it better on the screen.

EXAMPLE D.3 The activity_main.xml file

FIGURE D.1 shows the Logcat output when we run, in particular the order of execution. It shows that the Logcat output statement at line 19 of the MainActivity class executes before the doInBackground method of the TestTask class executes. This very simple simulation confirms that the correct way to process the value generated by a task is to do it inside the onPostExecute method. FIGURE D.2 shows the text inside the TextView has changed from Hello World! to Changed using AsyncTask.

FIGURE D.1 Logcat output of Example D.2

FIGURE D.2 The app running inside the emulator

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.245.44