Posts Tagged 'ui thread'

One Way to Cancel an AsyncTask

The AsyncTask class in Android provides an easy way to do a task in the background, without adversely impacting the main user interface thread. Generally speaking, it works really well for me. For a working example, refer to “Using Android AsyncTask For Background Tasks“. Recently, however, I have had a problem in cancelling the task.

Here’s my situation. I am working on a game. Part of the game involves a background task that computes moves for two players. As moves are computed, they are shown on the app’s screen by making calls to publishProgress. The calls occur in a loop.

while (gameNotOver) {
  ...
  publishProgress (player, move);
}

Back in the activity that starts the AsyncTask, I have a call to cancel the task: “t.cancel (true)”. To break out of the loop, I added calls to isCancelled.

if (isCancelled ()) break;

The problem arises because the UI thread’s call to cancel does not actually stop the task. It simply sets a flag in the AsyncTask and then arranges for a call to onCancelled. You certainly can do things in onCancelled that touch the user interface. That’s how AsyncTask was designed. However, the timing is a bit off. The cancel request is received very quickly, but in my case the background task keeps going until it checks with “isCancelled ()” and breaks out of the loop. That creates uncertainty in the activity running on the UI thread. Is the background task really stopped, or is it about to stop?

In my app, the call to cancel the background task is being made not to stop the game completely, but rather to pause the game so the user can look around. The UI has a Play and a Pause button, with cancel tied to the Pause button. When the user is done looking around, he or she selects the Play button to resume the game. That’s what the user thinks is happening. Behind the scenes, the code in the activity picks up the object that holds the state of the game and starts a new AsyncTask to continue from that state.

I do not want to have more than one active AsyncTask altering the game state. As described above, I found that onCancelled gets called immediately, but the real background task can take a bit longer to break out of the loop. To address this problem, I changed the way I used publishProgress in the background task. I added an extra argument to indicate the kind of progress information that was being sent. Inside the loop, I send “move” progress information and outside the loop I send “cancel” progress information if indeed the task was cancelled. It looks like the following code:

while (gameNotOver) {
  if (isCancelled () break;
  ...
  publishProgress (KIND_move, player, move);
}
if (isCancelled ()) {
  publishProgress (KIND_cancel, -1, -1);
}
return moveCount;

I don’t know if this is an ideal solution, but it does get the job done and works using the protocol defined by AsyncTask. The only thing that is a bit unconventional is the use of several integer arguments to report progress to the UI thread. Most examples I have seen use the arguments to report progress in terms of percent completed.

Using Android AsyncTask For Background Tasks

My last note (“Long Computations in the Main Android Thread“) explained one way to take an expensive operation and move it to a background task. Since writing that, I have learned about the AsyncTask class. It is much easier way to do the same thing.

AsyncTask gives you an easy way to start a background task to do something that you don’t want running on the UI thread. Complete documentation is available on the Android Developers website: AsyncTask. It’s pretty clear from that what you have to do.

If you are looking for a working example, I have built one and posted the source code at the end of this note. A few points about my example:

  • It illustrates the important aspects of AsyncTask: starting a task, publishing progress, etc.
  • It shows how to cancel the running task.
  • It shows how to cancel a running task when your activity is paused or stopped.

The app is shown in Figure 1. It has four rectangular views that change colors when you press the Start button. It runs for about 2000 iterations or until you press the Stop button.

Figure 1

It’s really not that computationally intensive to choose one of the four views at random, but I wanted to make sure I understood the mechanics in a simple case before I tried it out in the game I am working on. In the game, generating a move can take several seconds to calculate.

One of the reasons I didn’t try AsyncTask right away is that I thought it was well-suited for tracking progress of a background task but not for what I wanted: computing moves that need to be displayed on the screen. Once I saw that the publishProgress method could be used to send any kind of object you want, I realized that it could handle passing moves in a game from the background task to the UI.

My doInBackground method has the following loop in it:

 // Loop until cancelled or the max moves number is exceeded.
 while (!done) {
   ...
  // Use the progress int values to report which player moved
  // and what the move was.
   publishProgress (player, nextMove);
   ...
 } // end while
...

To be able to cancel the background task, you have to make calls to isCancelled in the loop. My example does this at the top of the loop.

if (isCancelled ()) break;

Back on the UI thread, you have to call the cancel method. I did this when the Stop button was clicked.

cancel (true);

Another thing to remember is that your activity can be paused at any time. For most apps, that means you should stop your background task. Check method onDestroy in the example and you will find call to stop the background task and cleanup.

endBackgroundTasks (true);

That method checks to see if a background task is running and cancels it if it is.

My initial version of this example had the app canceling the background task whenever onPause and onStop were called. Those are called whenever there is a change in orientation. After reading “Handling Progress Dialogs and Orientation Changes” (see references and comments below), I changed this example to keep the background task running as the orientation changes and even when you click the home button and start another app. When you return to this app, you see that the background task is still running or that it completed while you were away.

Accidentally Non-Responsive

One little mistake I made while testing turned out to be a good thing. Instead of starting the background task correctly with

 t.execute (1, 2);

I mistakenly used the doInBackground method directly.

  t.doInBackground (1, 2);

This error helped me see what a non-responsive app looks like. Starting with doInBackground means the background task is not being set up at all. So the long computation starts — in the UI thread —  and nothing changes on the screen and the buttons cease to work until the 1000 or so iterations finishes.

References

Handling Progress Dialogs and Orientation Changes
This is an excellent tutorial on AsyncTask. I suggest that you read it thoroughly.  Section 5 was especially useful to me when I reworked my example to handle orientation changes.

Source Code

The source code for this demo application is available in two places:  (1) download from Wglxy.com. (2) download from Google Docs.  If you find that the app does not build in Eclipse, be sure to do a clean build by using the “Clean” item on the Project menu.

This example app has been tested with the Android emulator and on an LG Optimus phone. The Android version is 2.2.

Long Computations in the Main Android Thread

In an Android application, you should work very hard to keep your application responsive to user actions (clicks, touches, scrolling, etc.).  That means not doing time-consuming operations like database queries or long computations in the main thread in which an activity runs.

Here are some good references for understanding this topic:

  • Painless Threading
    This is a good article on the Android Developers website. It covers the basic concepts for good user interface performance.  It is a great place to start.
  • Designing for Responsiveness
    Another article from Android Developers website. The key point for me: “any method that runs in the main thread should do as little work as possible.”
  • Intro to Loopers and Handlers
    This is an excellent explanation of Looper and Handler classes, which are classes that you use when you move tasks out of the user interface thread. The author explains a pattern that he calls the “Pipeline Thread”.

Five Simple Steps

1. Set up your activity so other threads can send messages to update user interface elements. Change the activity so it implements the android.os.Handler callback interface.

public class MyActivity extends Activity
implements Handler.Callback

2. Add an instance variable in your activity to hold a Handler. Then add the following to your activity’s onCreate method.

// Create a handler so other threads can  communicate with this one,
//  which is where UI operations take place.
mHandler = new Handler (this);

3. Implement the Handler.Callback method. This will be the place where you update views and widgets to keep the user informed of progress.

public boolean handleMessage (Message msg)

4. Take expensive operations and start them in a thread. For example, in an onClick method, add the following code:

new Thread(new Runnable() {
public void run() {
…  // something time-consuming
}  }).start();

Somewhere in the code that runs inside that thread, arrange to send a message back to the main activity. The message should convey something about the status of the ongoing operation. Exactly what that is depends on your application.

Message msg = Message.obtain (mHandler, what, arg1, arg2);
mHandler.sendMessage (msg);

5. Back in your activity, make sure that the message receiver is ready to handle the different kinds of messages that get sent. The “what” argument can be used to indicate the type of message. Something like:

public boolean handleMessage (Message msg) {
int kind = msg.what;
int arg1 = msg.arg1;
int arg2 = msg.arg2;
// update user interface views

};

I was surprised how easy it was to do this. A bit of research on the topic and about an hour editing and debugging was all it took.



Follow

Get every new post delivered to your Inbox.

Join 72 other followers