AsyncTasks, Runnables, Handlers, and Threads, oh my!

Not every Android app has to deal with multithreading, but any app that needs to contact Google, Facebook (or any other web-services API) or load data from a local database needs to consider how to properly handle method calls that are not guaranteed to terminate in any reasonable period of time. (The Android Developer’s Guide has an article on Painless Threading, which is a great starting point for understanding why threading can be important for apps with long-running operations.) While you technically can find ways of blocking the Android UI while long-running calls occur, it’s a bad practice which will cause performance issues and may lead to the unwanted ANR.

Now that we know we need to wrap our long-running method calls in some kind of background thread, the question becomes: when should we use an AsyncTask as opposed to a vanilla Thread with a Runnable? What’s a Handler all about? Who is responsible for keeping track of all these threads?

Let’s take a look at regular Threads first. Threads have been present in Java since the beginning, and they’re the most basic, straightforward way to implement a background operation in Android. This example tries to load data from an imaginary long web service call, trying again every 30 seconds if it encounters an exception:

new Thread(new Runnable() {
	public void run() {
		String myData = null;
		while (myData == null) {
			try {
				myData = longWebServiceCall();					
			} catch (Exception e) {
				// wait 30 seconds
				try {
					Thread.sleep(30000);
				} catch (InterruptedException ie) {
					// do nothing
				}
				// try again
				continue;
			}
		}
	}
}).start();

This is compact, resource-light, and should work fine in most scenarios, but there are a couple of warning signs that this might not be the best way to use threads. First, notice that there’s no reference being held to the Thread itself. If the Activity executing this code is interrupted (which can happen at any time in the app lifecycle), this thread could be left running and consuming resources, which would be unfortunate. Also notice that there’s no way of determining when the thread has completed and doing something with the data we just fetched from that long web service call! Let’s improve this example a bit, and add a Handler to do something with the data fetched by our Thread:

final Handler dataHandler = new Handler() {
	@Override
	public void handleMessage(Message msg) {
		String myData = (String) msg.obj;
		updateUI(myData);
};
		
Thread dataFetcher = new Thread(new Runnable() {
	public void run() {
		String myData = null;
		while (myData == null) {
			try {
				myData = longWebServiceCall();					
			} catch (Exception e) {
				// wait 30 seconds
				try {
					Thread.sleep(30000);
				} catch (InterruptedException ie) {
					// do nothing
				}
				// try again
				continue;
			}
			// send to our Handler
			Message msg = new Message();
			msg.obj = myData;
			versionHandler.sendMessage(msg);
		}
	}
});
dataFetcher.start();

You can use Handlers to communicate with threads in both directions, but in this trivial example we’re just taking the data received by the thread and using it to update the UI in some way. The Handler class has all kinds of nifty methods, but be sure to at least implement the handleMessage() method in order to receive messages from your Thread’s message queue!

Also notice that we gave our Thread a name. Keeping a reference to this Thread is important, so we can stop it nicely if its parent Activity pauses or quits. This can be accomplished by declaring the dataFetcher Thread as a private member of the parent Activity class, and call dataFetcher.stop() in the Activity’s onPause() method.

The above Thread/Runnable/Handler approach works great for small background tasks, but what if we may want to invoke this type of task many times, update the user on the task’s progress, or execute some code immediately before or after the main long-running call? In Android 1.5 and up, AsyncTask is available to make threading a lot easier. It comes at a cost of some additional system resources and (sensible, in my opinion) limitations. For example, AsyncTask manages its own thread pool with a maximum size of 128 threads (as of this writing). Unless you programmatically raise the limit, you’ll get an exception if you try to invoke a 129th concurrent thread. On a phone or tablet, invoking a huge number of simultaneous threads is not a very good idea from a resource standpoint, so I think AsyncTask is a great way to keep threads safe, sane, and under control. Let’s try reimplementing our trivial data-fetcher as an AsyncTask:

private class FetchDataTask extends AsyncTask {
	private Callback callback;
	
	public static interface Callback {
		public void onComplete(String myData);
	}
	
	@Override
	protected String doInBackground(Void... params) {
		if (this.isCancelled()) {
			return null;
		}
		String myData = null;
		while (myData == null) {
			try {
				myData = longWebServiceCall();					
			} catch (Exception e) {
				// wait 30 seconds
				try {
					Thread.sleep(30000);
				} catch (InterruptedException ie) {
					// do nothing
				}
				// try again
				continue;
			}
			return myData;
		}
		
		protected void onPostExecute(String result) {
			super.onPostExecute(result);
			callback.onComplete(result);
		}
	}

The AsyncTask documentation has much more detail on the meanings of the types. In our trivial example, we aren’t passing any parameters into the tasks and we aren’t incrementing any progress units, so we’re only using the last type, the Result, which is going to be a String in our case. Note that the main logic is implemented in the overridden method doInBackground(). The result of doInBackground() is passed to onPostExecute(), and we’re passing that result on to a callback which we are going to define in our main Activity. Since the Activity itself handles the callback, we can safely manipulate our UI elements on the main thread.

public class MyCoolActivity extends Activity 
		implements FetchDataTask.Callback {
	private ProgressDialog progress;
	private FetchDataTask fetchThread;
	
	// ...
	
	public void fetchData() {
		if (fetchThread.getStatus() != AsyncTask.Status.FINISHED) {
			fetchThread.cancel(true);
		}
		progress = ProgressDialog.show(getContext(), "Fetch", 
						"Fetching data...", true, false);
		fetchThread = (FetchDataTask) new FetchDataTask().execute();
	}
	
	public void onComplete(String myData) {
		if (progress.isShowing()) {
			progress.dismiss();
		}
		updateUI(myData);
	}
}

You’ll still want to make sure to clean up your threads in onPause() as described earlier, but AsyncTask can provide some convenient advantages for implementing certain long-running background tasks. The main points to remember when working with Android threads of any kind are:

  1. Don’t block the UI thread!! Analyze your code to determine where you need to wrap long-running calls in background tasks.
  2. Be sure to hang onto a reference to your threads, so you can stop them cleanly if your Activity pauses.
  3. Only access the UI on the main UI thread. Android 3.0 and above is very particular about this.

Comments

  1. In general using the raw Thread/Runnable API at the application level is no longer recommended practice. Take a look at Doug Lea’s book Concurrent Programming in Java and the follow-on Java Concurrency in Practice. Since Java 5.0 the java.util.concurrent package is the preferred API for concurrent programming.

    The AsyncTask in Android is convenient for starting and interacting with other threads from the UI, but the rule about interacting with the UI only from the UI thread has been true since the early days of Swing, if not before.

Comments are closed.