Chapter 15

Dealing with Threads

Ideally, you want your activities to be downright snappy, so your users don't feel that your application is sluggish. Responding to user input quickly (e.g., within in 200 milliseconds) is a fine goal. At minimum, though, you need to make sure you respond within 5 seconds, lest the ActivityManager decide to play the role of the Grim Reaper and kill off your activity as being nonresponsive.

Of course, your activity might have real work to do, which takes nonnegligible amount of time. This invariably involves the use of a background thread. Android provides a veritable cornucopia of means to set up background threads, yet allow them to safely interact with the UI on the UI thread.

The “safely interact” concept is crucial. You cannot modify any part of the UI from a background thread. That must be done on the UI thread. This generally means that there will need to be some coordination between background threads doing the work and the UI thread showing the results of that work.

This chapter covers how to work with background and UI threads in your Android applications.

Getting Through the Handlers

The most flexible means of making an Android-friendly background thread is to create an instance of a Handler subclass. You need only one Handler object per activity, and you do not need to manually register it. Merely creating the instance is sufficient to register it with the Android threading subsystem.

Your background thread can communicate with the Handler, which will do all of its work on the activity's UI thread. This is important, as UI changes, such as updating widgets, should occur only on the activity's UI thread.

You have two options for communicating with the Handler: messages and Runnable objects.

Messages

To send a Message to a Handler, first invoke obtainMessage() to get the Message object out of the pool. There are a few flavors of obtainMessage(), allowing you to create empty Message objects or ones populated with message identifiers and arguments. The more complicated your Handler processing needs to be, the more likely it is you will need to put data into the Message to help the Handler distinguish different events.

Then you send the Message to the Handler via its message queue, using one of the sendMessage…() family of methods, such as the following:

  • sendMessage(): Puts the message on the queue immediately.
  • sendMessageAtFrontOfQueue(): Puts the message on the queue immediately, placing it at the front of the message queue, so your message takes priority over all others.
  • sendMessageAtTime(): Puts the message on the queue at the stated time, expressed in the form of milliseconds based on system uptime (SystemClock.uptimeMillis()).
  • sendMessageDelayed(): Puts the message on the queue after a delay, expressed in milliseconds.

To process these messages, your Handler needs to implement handleMessage(), which will be called with each message that appears on the message queue. There, the handler can update the UI as needed. However, it should still do that work quickly, as other UI work is suspended until the Handler is finished.

For example, let's create a ProgressBar and update it via a Handler. Here is the layout from the Threads/Handler sample project:

<?xml version="1.0" encoding="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"
  >
  <ProgressBar android:id="@+id/progress"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
</LinearLayout>

The ProgressBar, in addition to setting the width and height as normal, also employs the style property. This particular style indicates the ProgressBar should be drawn as the traditional horizontal bar showing the amount of work that has been completed.

And here is the Java:

package com.commonsware.android.threads;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;

public class HandlerDemo extends Activity {
  ProgressBar bar;
  Handler handler=new Handler() {
    @Override
    public void handleMessage(Message msg) {
      bar.incrementProgressBy(5);
    }
  };
  boolean isRunning=false;

  @Override
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);
    bar=(ProgressBar)findViewById(R.id.progress);
  }

  public void onStart() {
    super.onStart();
    bar.setProgress(0);

    Thread background=new Thread(new Runnable() {
      public void run() {
        try {
          for (int i=0;i<20 && isRunning;i++) {
            Thread.sleep(1000);
            handler.sendMessage(handler.obtainMessage());
          }
        }
        catch (Throwable t) {
          // just end the background thread
        }
      }
    });

    isRunning=true;
    background.start();
  }

  public void onStop() {
    super.onStop();
    isRunning=false;
  }
}

As part of constructing the Activity, we create an instance of Handler, with our implementation of handleMessage(). Basically, for any message received, we update the ProgressBar by 5 points, and then exit the message handler.

In onStart(), we set up a background thread. In a real system, this thread would do something meaningful. Here, we just sleep 1 second, post a Message to the Handler, and repeat for a total of 20 passes. This, combined with the 5-point increase in the ProgressBar position, will march the bar clear across the screen, as the default maximum value for ProgressBar is 100. You can adjust that maximum via setMax(). For example, you might set the maximum to be the number of database rows you are processing, and update once per row.

Note that we then leave onStart(). This is crucial. The onStart() method is invoked on the activity UI thread, so it can update widgets and such. However, that means we need to get out of onStart(), both to let the Handler get its work done and also so Android does not think our activity is stuck.

The resulting activity is simply a horizontal progress bar, as shown in Figure 15–1.

image

Figure 15–1. The HandlerDemo sample application

Note, though, that while ProgressBar samples like this one show your code arranging to update the progress on the UI thread, for this specific widget, that is not necessary. At least as of Android 1.5, ProgressBar is now UI thread-safe, in that you can update it from any thread, and it will handle the details of performing the actual UI update on the UI thread.

Runnables

If you would rather not fuss with Message objects, you can also pass Runnable objects to the Handler, which will run those Runnable objects on the activity UI thread. Handler offers a set of post…() methods for passing Runnable objects in for eventual processing.

Running in Place

Just as Handler supports post() and postDelayed() to add Runnable objects to the event queue, you can use those same methods on View. This slightly simplifies your code, in that you can then skip the Handler object. However, you lose a bit of flexibility. Also, the Handler has been in the Android toolkit longer, and it may be more tested.

Where Oh Where Has My UI Thread Gone?

Sometimes, you may not know if you are currently executing on the UI thread of your application. For example, if you package some of your code in a JAR file for others to reuse, you might not know whether your code is being executed on the UI thread or from a background thread.

To help combat this problem, Activity offers runOnUiThread(). This works similar to the post() methods on Handler and View, in that it queues up a Runnable to run on the UI thread, if you are not on the UI thread right now. If you are already on the UI thread, it invokes the Runnable immediately. This gives you the best of both worlds: no delay if you are on the UI thread, yet safety in case you are not.

Asyncing Feeling

Android 1.5 introduced a new way of thinking about background operations: AsyncTask. In one (reasonably) convenient class, Android will handle all of the chores of doing work on the UI thread versus on a background thread. Moreover, Android itself allocates and removes that background thread. And it maintains a small work queue, further accentuating the fire-and-forget feel to AsyncTask.

The Theory

There is a saying, popular in marketing circles: “When a man buys a 1/4-inch drill bit at a hardware store, he does not want a 1/4-inch drill bit—he wants 1/4-inch holes.” Hardware stores cannot sell holes, so they sell the next-best thing: devices (drills and drill bits) that make creating holes easy.

Similarly, Android developers who have struggled with background thread management do not strictly want background threads. Rather, they want work to be done off the UI thread, so users are not stuck waiting and activities do not get the dreaded “application not responding” (ANR) error. And while Android cannot magically cause work to not consume UI thread time, it can offer things that make such background operations easier and more transparent. AsyncTask is one such example.

To use AsyncTask, you must:

  • Create a subclass of AsyncTask, commonly as a private inner class of something that uses the task (e.g., an activity).
  • Override one or more AsyncTask methods to accomplish the background work, plus whatever work associated with the task that needs to be done on the UI thread (e.g., update progress).
  • When needed, create an instance of the AsyncTask subclass and call execute() to have it begin doing its work.

What you do not need to do is:

  • Create your own background thread.
  • Terminate that background thread at an appropriate time.
  • Call all sorts of methods to arrange for bits of processing to be done on the UI thread.

AsyncTask, Generics, and Varargs

Creating a subclass of AsyncTask is not quite as easy as, say, implementing the Runnable interface. AsyncTask uses generics, and so you need to specify three data types:

  • The type of information that is needed to process the task (e.g., URLs to download)
  • The type of information that is passed within the task to indicate progress
  • The type of information that is passed when the task is completed to the post-task code

What makes this all the more confusing is that the first two data types are actually used as varargs, meaning that an array of these types is used within your AsyncTask subclass.

This should become clearer as we work our way toward an example.

The Stages of AsyncTask

There are four methods you can override in AsyncTask to accomplish your ends.

The one you must override, for the task class to be useful, is doInBackground(). This will be called by AsyncTask on a background thread. It can run as long as necessary in order to accomplish whatever work needs to be done for this specific task. Note, though, that tasks are meant to be finite; using AsyncTask for an infinite loop is not recommended.

The doInBackground() method will receive, as parameters, a varargs array of the first of the three data types listed in the preceding section—the data needed to process the task. So, if your task's mission is to download a collection of URLs, doInBackground() will receive those URLs to process. The doInBackground() method must return a value of the third data type listed—the result of the background work.

You may wish to override onPreExecute(). This method is called, from the UI thread, before the background thread executes doInBackground(). Here, you might initialize a ProgressBar or otherwise indicate that background work is commencing.

Also, you may wish to override onPostExecute(). This method is called, from the UI thread, after doInBackground() completes. It receives, as a parameter, the value returned by doInBackground() (e.g., success or failure flag). Here, you might dismiss the ProgressBar and make use of the work done in the background, such as updating the contents of a list.

In addition, you may wish to override onProgressUpdate(). If doInBackground() calls the task's publishProgress() method, the object(s) passed to that method are provided to onProgressUpdate(), but in the UI thread. That way, onProgressUpdate() can alert the user as to the progress that has been made on the background work, such as updating a ProgressBar or continuing an animation. The onProgressUpdate() method will receive a varargs of the second data type from the list in the preceding section—the data published by doInBackground() via publishProgress().

A Sample Task

As mentioned earlier, implementing an AsyncTask is not quite as easy as implementing a Runnable. However, once you get past the generics and varargs, it is not too bad.

For example, the following is an implementation of a ListActivity that uses an AsyncTask, from the Threads/Asyncer sample project:

package com.commonsware.android.async;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;

public class AsyncDemo extends ListActivity {
  private static String[] items={"lorem", "ipsum", "dolor",
                                 "sit", "amet", "consectetuer",
                                 "adipiscing", "elit", "morbi",
                                 "vel", "ligula", "vitae",
                                 "arcu", "aliquet", "mollis",
                                 "etiam", "vel", "erat",
                                 "placerat", "ante",
                                 "porttitor", "sodales",
                                 "pellentesque", "augue",
                                 "purus"};
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    setListAdapter(new ArrayAdapter<String>(this,
                       android.R.layout.simple_list_item_1,
                       new ArrayList()));

    new AddStringTask().execute();
  }

  class AddStringTask extends AsyncTask<Void, String, Void> {
    @Override
    protected Void doInBackground(Void... unused) {
      for (String item : items) {
        publishProgress(item);
        SystemClock.sleep(200);
      }
      
      return(null);
    }

    @Override
    protected void onProgressUpdate(String... item) {
      ((ArrayAdapter)getListAdapter()).add(item[0]);
    }

    @Override
    protected void onPostExecute(Void unused) {
      Toast
        .makeText(AsyncDemo.this, "Done!", Toast.LENGTH_SHORT)
        .show();
    }
  }
}

This is another variation on the lorem ipsum list of words, used frequently throughout this book. This time, rather than simply hand the list of words to an ArrayAdapter, we simulate needing to work to create these words in the background using AddStringTask, our AsyncTask implementation.

If you build, install, and run this project, you will see the list being populated in real time over a few seconds, followed by a Toast indicating completion, as shown in Figure 15–2.

image

Figure 15–2. The AsyncDemo, partway through loading the list of words

Let's examine this project's code piece by piece.

The AddStringTask Declaration

First, let's look at the AddStringTask declaration:

class AddStringTask extends AsyncTask<Void, String, Void> {

Here, we use the generics to set up the specific types of data we are going to leverage in AddStringTask, as follows:

  • We do not need any configuration information in this case, so our first type is Void.
  • We want to pass each string generated by our background task to onProgressUpdate(), to allow us to add it to our list, so our second type is String.
  • We do not have any results, strictly speaking (beyond the updates), so our third type is Void.
The doInBackground() Method

Next up is the doInBackground() method:

@Override
protected Void doInBackground(Void... unused) {
  for (String item : items) {
    publishProgress(item);
    SystemClock.sleep(200);
  }

  return(null);
}

The doInBackground() method is invoked in a background thread. Hence, we can take as long as we like. In a production application, we might be doing something like iterating over a list of URLs and downloading each. Here, we iterate over our static list of lorem ipsum words, call publishProgress() for each, and then sleep 1/4 second to simulate real work being done.

Since we elected to have no configuration information, we should not need parameters to doInBackground(). However, the contract with AsyncTask says we need to accept a varargs of the first data type, which is why our method parameter is Void… unused.

Since we elected to have no results, we should not need to return anything. Again, though, the contract with AsyncTask says we must return an object of the third data type. Since that data type is Void, our returned object is null.

The onProgressUpdate() Method

The onProgressUpdate() method looks like this:

@Override
protected void onProgressUpdate(String... item) {
  ((ArrayAdapter)getListAdapter()).add(item[0]);
}

The onProgressUpdate() method is called on the UI thread, and we want to do something to let the user know we are making progress on loading these strings. In this case, we simply add the string to the ArrayAdapter, so it is appended to the end of the list.

The onProgressUpdate() method receives a String... varargs because that is the second data type in our class declaration. Since we are passing only one string per call to publishProgress(), we need to examine just the first entry in the varargs array.

The onPostExecute() Method

Here's the onPostExecute() method:

@Override
protected void onPostExecute(Void unused) {
  Toast
    .makeText(AsyncDemo.this, "Done!", Toast.LENGTH_SHORT)
    .show();
}

The onPostExecute() method is called on the UI thread, and we want to do something to indicate that the background work is complete. In a real system, there may be some ProgressBar to dismiss or some animation to stop. Here, we simply raise a Toast.

Since we elected to have no results, we should not need any parameters. The contract with AsyncTask says we must accept a parameter of the third data type. Since that data type is Void, our method parameter is Void unused.

The Activity

Finally, let's look at the activity:

new AddStringTask().execute();

To use AddStringsTask, we simply create an instance and call execute() on it. That starts the chain of events eventually leading to the background thread doing its work.

If AddStringsTask required configuration parameters, we would not have used Void as our first data type, and the constructor would accept zero or more parameters of the defined type. Those values would eventually be passed to doInBackground().

And Now, the Caveats

Background threads, while eminently possible using the Android Handler system, are not all happiness and warm puppies. Background threads not only add complexity, but they also have real-world costs in terms of available memory, CPU, and battery life. Hence, there are a wide range of scenarios you need to account for with your background thread, including the following:

  • The possibility that users will interact with your activity's UI while the background thread is chugging along. If the work that the background thread is doing is altered or invalidated by the user input, you will need to communicate this to the background thread. Android includes many classes in the java.util.concurrent package that will help you communicate safely with your background thread.
  • The possibility that the activity will be killed off while background work is occurring. For example, after starting your activity, the user might have a call come in, followed by a text message, followed by a need to look up a contact—all of which might be sufficient to kick your activity out of memory. Chapter 16 will cover the various events Android will take your activity through. Hook to the proper ones, and be sure to shut down your background thread cleanly when you have the chance.
  • The possibility that your user will get irritated if you chew up a lot of CPU time and battery life without giving any payback. Tactically, this means using ProgressBar or other means of letting the user know that something is happening. Strategically, this means you still need to be efficient at what you do—background threads are no panacea for sluggish or pointless code.
  • The possibility that you will encounter an error during background processing. For example, if you are gathering information from the Internet, the device might lose connectivity. Alerting the user of the problem via a notification (discussed in Chapter 31) and shutting down the background thread may be your best option.
..................Content has been hidden....................

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