Introduction
After SDK 1.5, an API was made available for executing asynchronous tasks in Android Activities, without worrying with threads and manners to access the UI. For that, one must use AsyncTasks. Here a simple example is provided of how to properly use this API. Later, it will be discussed when to use (and when not to use) this API.
Background
In order to understand the importance of this API, one has to realize that when executing long tasks in Android Activities, the UI cannot be locked while the task is being performed. The UI has to be accessible so the user does not have the impression that the program being executed is frozen. Furthermore, when executing time-consuming tasks, the user may have to be informed of the progress of this very task. Thus, to accomplish all these goals, one has to utilize the AsyncTasks API, which allows a long job to be executed while its progress is being displayed and the interface is still accessible for the user to interact.
Using the Code
In order to use the AsyncTask API, one has to follow the steps described below:
- Create a class which extends AsyncTask.
- Fill in the generic types available as generics in the class for:
- the task execution array parameters
- progress array parameters
- result array parameters
- Implement the method
doInBackground(Parameters... parameters)
. This method must execute the job which is supposed to be quite demanding. - Optionally, one can implement methods for:
- cancelling the task -
onCancelled(...)
- executing tasks before the demanding task -
onPreExecute(...)
- reporting progress -
onProgressUpdate(...)
- executing activities after the demanding task is finished -
onPostExecute(...)
.
Having that in mind, now a sample will be given which the code shows what was just enumerated above.
Displayed below is the XML Layout of the Activity
which holds the long-task to-be-executed. This activity has a Start/Restart and Cancel buttons, which obviously starts and cancels the task respectively. It also holds a label, which states the task status and a progress bar which actually shows the progress in terms or complete percentage.
="1.0"="utf-8"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/txtMessage"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="304dp"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/btnRestart"
android:onClick="restartOnclick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Restart" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="cancelOnclick"
android:text="Cancel" />
</LinearLayout>
</LinearLayout>
Given the Activity´s XML Layout, the Activity implementation will be depicted below. Note that the code is widely commented so one can easily understand it. The full application is also available for download in this article. Now, further explanations will be given regarding the most important parts of the code.
package com.asynctask;
import java.text.MessageFormat;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button btnRestart;
private Button btnCancel = null;
private TextView txtMessage = null;
private ProgressBar mProgressBar = null;
private HugeWork task = null;
private static final int MAX_PROGRESS = 10;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnRestart = (Button) findViewById(R.id.btnRestart);
btnCancel = (Button) findViewById(R.id.btnCancel);
txtMessage = (TextView) findViewById(R.id.txtMessage);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mProgressBar.setMax(MAX_PROGRESS);
start();
}
public void cancelOnclick(View v) {
task.cancel(true);
btnCancel.setEnabled(false);
btnRestart.setEnabled(true);
}
public void restartOnclick(View v) {
start();
}
private void start() {
task = new HugeWork();
task.execute(0);
mProgressBar.setProgress(0);
btnCancel.setEnabled(true);
btnRestart.setEnabled(false);
}
private void executeHardWork() {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
class HugeWork extends AsyncTask<Integer, Integer, Integer> {
@Override
protected void onPreExecute() {
txtMessage.setText("Executing async task...");
super.onPreExecute();
}
@Override
protected Integer doInBackground(Integer... params) {
int progress = ((Integer[])params)[0];
do {
if (!this.isCancelled()) {
executeHardWork();
}
else {
break;
}
progress++;
publishProgress(progress);
} while (progress <= MAX_PROGRESS);
return progress;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = ((Integer[])values)[0];
mProgressBar.setProgress(progress);
super.onProgressUpdate(values);
}
@Override
protected void onCancelled(Integer result) {
txtMessage.setText(MessageFormat.format
("Async task has been cancelled at {0} seconds.", result - 1));
super.onCancelled(result);
}
@Override
protected void onPostExecute(Integer result) {
txtMessage.setText(MessageFormat.format
("Async task execution finished in {0} seconds.", result - 1));
btnCancel.setEnabled(false);
btnRestart.setEnabled(true);
super.onPostExecute(result);
}
}
}
The method shown below represents the core of the asynchronous functionality. This method executes the long-time task (calling the time-consuming-method executeHardWork()
) and report its progress using the method publishProgress(progress)
- which makes the event onProgressUpdate(...)
to be executed.. Note also that here it is verified whether the task was cancelled, before it proceeds.
Thus, here all which is desired for a long-period-asynchronous task execution is accomplished. The job does not freeze the UI, it can be cancelled and its progress is reported.
@Override
protected Integer doInBackground(Integer... params) {
int progress = ((Integer[])params)[0];
do {
if (!this.isCancelled()) {
executeHardWork();
}
else {
break;
}
progress++;
publishProgress(progress);
} while (progress <= MAX_PROGRESS);
return progress;
}
Continuing, have a look at the method displayed below. It interacts with the UI displaying the progress made. Note that the percentage of the progress is indeed informed by the variable <code>progress
. Moreover, observe that this method, the onProgressUpdate(...)
was called by the publishProgress(progress)
, as mentioned earlier.
@Override
protected void onProgressUpdate(Integer... values) {
int progress = ((Integer[])values)[0];
mProgressBar.setProgress(progress);
super.onProgressUpdate(values);
}
Another method worth mentioning is the onCancelled(Integer)
. This event is called and the cancellation is performed. This method simply displays a message stating the new status (cancellation). Notice also, that when the cancellation is dispatched, the method doInBackground(...)
stops executing when the isCancelled()
verification returns true
. It is important to emphasize that, when the call off occurs, the method onPostExecute(...)
is not called. In the code, the cancellation was triggered by the Cancel Button event cancelOnclick(View)
. The method called in the AsyncTask
object was AsyncTask.cancel(boolean)
.
@Override
protected void onCancelled(Integer result) {
txtMessage.setText(MessageFormat.format
("Async task has been cancelled at {0} seconds.", result - 1));
super.onCancelled(result);
}
The last method discussed is the onPostExecute(Integer)
. It executes the necessary work after the task is finished. Here it displays a finish message and resets the form so the job can be started over. Again, this method is not called when the task is cancelled, as stated before.
@Override
protected void onPostExecute(Integer result) {
txtMessage.setText(MessageFormat.format
("Async task execution finished in {0} seconds.", result - 1));
btnCancel.setEnabled(false);
btnRestart.setEnabled(true);
super.onPostExecute(result);
}
When to Use this API
After reading this article, one may be wondering whether this manner of interacting with the UI should be used with every action taken by the user. For instance, suppose your UI displays a list of items and you can edit, delete, update or make changes in a set of them. For each of these actions, one may be inclined to use this API, believing that by doing so, there will be no risk of the user interacting with a frozen form. The answer to this issue is: no, one does not have to use this API for each user action, and here are the reasons:
- Most of the user actions will have a quick response, thus making it much simpler to just implement the event without worrying about frozen windows.
- The code is much harder to maintain. Think about the situation just described where one has a list in which many actions can be taken. The code would be tediously repetitive, prompted to many bugs and most of the code written would be rendered useless because there is no need for fancy asynchronous interactions.
However, one should be aware of the scenarios where this API is mandatory for a good user experience. These cases fall into a simple rule, which is obvious but deserves to be discussed: whenever an action will take too long - more than one or two seconds - the user must be warned that a job is still being executed. Here are the cases where these situations are most likely to happen:
- The activity involves network communication. Whenever the application has to interact with the internet or local network, there most likely will be a delay. Thus one should use the AsyncTask API.
- The application is starting and heavy processing is being made. This occurs mainly in games. Note that when most of the fanciest games open, they take a while to start. This is the ideal scenario to show a progress bar, a label stating what is happening, and a waiting message.
Conclusions
This article explained in details how the Android API for asynchronous tasks works. This API is very interesting because the resulting code is quite organized and something which could be quite hard to implement using threads is straightforward and easy to maintain. Finally, here we also discussed when it is appropriate to use this API and when it is not.