Users like snappy applications. Users do not like applications that feel sluggish. The way to help make your application feel snappy to users is to use the standard threading capabilities built into Android. This chapter will walk you through the issues involved with thread management in Android and some of the options for keeping the UI crisp and responsive.
You might think that when you call setText()
on a TextView
, the screen is updated with the text you supply, right then and there. That is not how it works. Rather, everything that modifies the widget-based UI goes through a message queue. Calls to setText()
do not update the screen; they just pop a message on a queue telling the operating system to update the screen. The operating system pops these messages off of this queue and does what the messages require.
The queue is processed by one thread, variously called the main application thread and the UI thread. As long as that thread can keep processing messages, the screen will update, user input will be handled, and so on.
However, the main application thread is also used for nearly all callbacks into your activity. Your onCreate()
, onClick()
, onListItemClick()
, and similar methods are all called on the main application thread. While your code is executing in these methods, Android is not processing messages on the queue, meaning the screen does not update, user input is not handled, and so on.
This, of course, is bad. So bad, in fact, that if you take more than a few seconds to do work on the main application thread, Android may display the dreaded “application not responding” (ANR) error, and your activity may be killed off. Hence, you want to make sure that all of your work on the main application thread happens quickly. This means that anything slow should be done in a background thread, so as not to tie up the main application thread. This includes activities such as the following:
Fortunately, Android supports threads using the standard Thread
class from Java, plus all the wrappers and control structures you would expect, such as the java.util.concurrent
class package.
However, there is one big limitation: you cannot modify the UI from a background thread. You can modify the UI only from the main application thread. Hence, you need to move long-running work into background threads, but those threads need to do something to arrange to update the UI using the main application thread. Android provides a wide range of tools to do just that, and these tools are the primary focus of this chapter.
If you are going to fork background threads to do work on behalf of the user, you should consider keeping the user informed that work is going on. This is particularly true if the user is effectively waiting for that background work to complete.
The typical approach to keeping users informed of progress is some form of progress bar, like you see when you copy a bunch of files from place to place in many desktop operating systems. Android supports this through the ProgressBar
widget.
A ProgressBar
keeps track of progress, defined as an integer, with 0
indicating no progress has been made. You can define the maximum end of the range—which value indicates progress is complete—via setMax()
. By default, a ProgressBar
starts with a progress of 0
, though you can start from some other position via setProgress()
. If you prefer your progress bar to be indeterminate, use setIndeterminate()
and set it to true
.
In your Java code, you can either positively set the amount of progress that has been made (via setProgress()
) or increment the progress from its current amount (via incrementProgressBy()
). You can find out how much progress has been made via getProgress()
.
There are other alternatives for displaying progress—ProgressDialog
, a progress indicator in the activity's title bar, and so on—but a ProgressBar
is a good place to start.
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.
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 immediatelysendMessageAtFrontOfQueue()
: Puts the message on the queue immediately and places it at the front of the message queue (versus the back, which is the default), so your message takes priority over all otherssendMessageAtTime()
: 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 millisecondssendEmptyMessage()
: Sends an empty Message
object to the queue, allowing you to skip the obtainMessage()
step if you were planning on leaving it empty anywayTo 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;
import java.util.concurrent.atomic.AtomicBoolean;
public class HandlerDemo extends Activity {
ProgressBar bar;
Handler handler=new Handler() {
@Override
public void handleMessage(Message msg) {
bar.incrementProgressBy(5);
}
};
AtomicBoolean isRunning=new AtomicBoolean(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.get();i++) {
Thread.sleep(1000);
handler.sendMessage(handler.obtainMessage());
}
}
catch (Throwable t) {
// just end the background thread
}
}
});
isRunning.set(true);
background.start();
}
public void onStop() {
super.onStop();
isRunning.set(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.
We then take advantage of onStart()
and onStop()
. 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 to inform Android that our activity is not stuck.
The resulting activity is simply a horizontal progress bar, as shown in Figure 20–1.
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.
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.
Just as Handler
supports post()
and postDelayed()
to add Runnable
objects to the event queue, you can use those same methods on any View
(i.e., any widget or container). This slightly simplifies your code, in that you can then skip the Handler
object.
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 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 similarly 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 already are 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.
Android 1.5 introduced a new way of thinking about background operations: AsyncTask
. In one (reasonably) convenient class, Android handles 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
.
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 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 do the following:
AsyncTask
, commonly as a private inner class of something that uses the task (e.g., an activity)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)AsyncTask
subclass and call execute()
to have it begin doing its workWhat you do not have to do is
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:
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.
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 is necessary 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 in the preceding section—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 preceding list—the data published by doInBackground()
via publishProgress()
.
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 final 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 having to work to create these words in the background using AddStringTask
, our AsyncTask
implementation.
Let's examine this project's code piece by piece.
The AddStringTask
declaration is as follows:
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
:
Void
.onProgressUpdate()
, to allow us to add it to our list, so our second type is String
.Void
.The doInBackground()
method is next in the code:
@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 200 milliseconds 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 must 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
.
Next up is the onProgressUpdate()
method:
@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 only the first entry in the varargs array.
The next method is onPostExecute()
:
@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 single value of the third data type. Since that data type is Void
, our method parameter is Void unused
.
The activity is as follows:
new AddStringTask().execute();
To use AddStringTask
, 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 AddStringTask
required configuration parameters, we would have not 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()
.
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 20–2.
One problem with the default destroy-and-create cycle that activities go through on an orientation change comes from background threads. If the activity has started some background work—through an AsyncTask
, for example—and then the activity is destroyed and re-created, the AsyncTask
needs to know about this somehow. Otherwise, the AsyncTask
might well send updates and final results to the old activity, with the new activity none the wiser. In fact, the new activity might start the background work again, wasting resources.
One way to deal with this is to disable the destroy-and-create cycle, by taking over configuration changes, as described in a previous section. Another alternative is to have a smarter activity and AsyncTask
. You can see an example of that in the Rotation/RotationAsync
sample project. As shown next, this project uses a ProgressBar
, much like the Handler
demo from earlier in this chapter. It also has a TextView
to indicate when the background work is completed, initially invisible.
<?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"
/>
<TextView android:id="@+id/completed"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Work completed!"
android:visibility="invisible"
/>
</LinearLayout>
The “business logic” is for an AsyncTask
to do some (fake) work in the background, updating the ProgressBar
along the way, and making the TextView
visible when it is finished. More importantly, it needs to do this in such a way as to behave properly if the screen is rotated. This means the following:
AsyncTask
, having it continue doing work and updating the wrong activity.AsyncTask
, thereby doubling our workload.Earlier, this chapter showed the use of an AsyncTask
that was implemented as a regular inner class of the Activity
class. That works well when you are not concerned about rotation. For example, if the AsyncTask
is not affecting the UI—such as uploading a photo—rotation will not be an issue for you. Having the AsyncTask
as an inner class of the Activity
means you get ready access to the Activity for any place where you need a Context
.
However, for the rotation scenario, a regular inner class will work poorly. The AsyncTask
will think it knows which Activity
it is supposed to work with, but in reality it will be holding onto an implicit reference to the old activity, not one after an orientation change.
So, in RotationAsync
, the RotationAwareTask
class is a static inner class. This means RotationAwareTask
does not have any implicit reference to any RotationAsyncActivity
(old or new):
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
public class RotationAsync extends Activity {
private ProgressBar bar=null;
private RotationAwareTask task=null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bar=(ProgressBar)findViewById(R.id.progress);
task=(RotationAwareTask)getLastNonConfigurationInstance();
if (task==null) {
task=new RotationAwareTask(this);
task.execute();
}
else {
task.attach(this);
updateProgress(task.getProgress());
if (task.getProgress()>=100) {
markAsDone();
}
}
}
@Override
public Object onRetainNonConfigurationInstance() {
task.detach();
return(task);
}
void updateProgress(int progress) {
bar.setProgress(progress);
}
void markAsDone() {
findViewById(R.id.completed).setVisibility(View.VISIBLE);
}
static class RotationAwareTask extends AsyncTask<Void, Void, Void> {
RotationAsync activity=null;
int progress=0;
RotationAwareTask(RotationAsync activity) {
attach(activity);
}
@Override
protected Void doInBackground(Void... unused) {
for (int i=0;i<20;i++) {
SystemClock.sleep(500);
publishProgress();
}
return(null);
}
@Override
protected void onProgressUpdate(Void... unused) {
if (activity==null) {
Log.w("RotationAsync", "onProgressUpdate() skipped – no activity");
}
else {
progress+=5;
activity.updateProgress(progress);
}
}
@Override
protected void onPostExecute(Void unused) {
if (activity==null) {
Log.w("RotationAsync", "onPostExecute() skipped – no activity");
}
else {
activity.markAsDone();
}
}
void detach() {
activity=null;
}
void attach(RotationAsync activity) {
this.activity=activity;
}
int getProgress() {
return(progress);
}
}
}
Since we want RotationAwareTask
to update the current RotationAsyncActivity
, we supply that Activity
when we create the task, via the constructor. RotationAwareTask
also has attach()
and detach()
methods to change which Activity
the task knows about, as we will see shortly.
When RotationAsync
starts up for the first time, it creates a new instance of the RotationAwareTask
class and executes it. At this point, the task has a reference to the RotationAsyncActivity
and can do its (fake) work, telling RotationAsync
to update the progress along the way.
Now, suppose that during the middle of the doInBackground()
processing, the user rotates the screen. Our Activity
will be called with onRetainNonConfigurationInstance()
. Here, we want to do two things:
Activity
instance is being destroyed, we need to make sure the task no longer holds onto a reference to it. Hence, we call detach()
, causing the task to set its RotationAsync
data member (activity
) to null
.RotationAwareTask
object, so that our new RotationAsync
instance can get access to it.Eventually, the new RotationAsync
instance will be created. In onCreate()
, we try to get access to any current RotationAwareTask
instance via getLastNonConfigurationInstance()
. If that was null
, then we know that this is a newly created activity, and so we create a new task. If, however, getLastNonConfigurationInstance()
returned the task object from the old RotationAsync
instance, we hold onto it and update our UI to reflect the current progress that has been made. We also attach()
the new RotationAsync
to the RotationAwareTask
, so as further progress is made, the task can notify the proper activity.
The net result is that our ProgressBar
smoothly progresses from 0
to 100
, even while rotations are going on.
Most callback methods in Android are driven by messages on the message queue being processed by the main application thread. Normally, this queue is being processed whenever the main application thread is not otherwise busy, such as running our code. However, when a configuration change occurs, like a screen rotation, that no longer holds true. In between the call to the onRetainNonConfigurationInstance()
instance of the old activity and the completion of onCreate()
of the new activity, the message queue is left alone.
So, let's suppose that, in between onRetainNonConfigurationInstance()
activity and the subsequent onCreate()
, our AsyncTask
's background work completes. This will trigger onPostExecute()
to be called...eventually. However, since onPostExecute()
is actually launched from a message on the message queue, onPostExecute()
will not be called until after our onCreate()
has completed. Hence, our AsyncTask
can keep running during the configuration change, as long as we do two things:
onCreate()
of the new activity instance, we update the AsyncTask
to have it work with our new activity, rather than the old one.doInBackground()
.Background threads, while eminently possible using the Android Handler
system, are not all happiness and warm puppies. Background threads not only add complexity, but also have real-world costs in terms of available memory, CPU, and battery life. Hence, you need to account for a wide range of scenarios with your background thread, including the following:
java.util.concurrent
package that will help you communicate safely with your background thread.ProgressBar
or other means of letting users 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.