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.
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 } );
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.
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
.
3.142.245.44