Wednesday, March 09, 2011

Android Background Processing

Background processing on mobile devices can be tough -- services, threads, etc, can be killed on a whim when memory/CPU resources start getting low, the device goes to sleep, network connectivity is less reliable, etc.

There are multiple ways to handle background processing in Android. Here's a QUICK overview of each (mainly so I can get it clear in my mind.)

Runnable/Threads
Standard Java threading. Just create a new thread and start it. The issue here is that it is difficult to communicate things back to the UI. You end up having to use "handlers" to pass messages to the UI.


import android.os.Handler;
private void testThread() {
final int PRE_EXECUTE = 1;
final int PROGRESS_UPDATE = 2;
final int POST_EXECUTE = 3;
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == PRE_EXECUTE)
Log.d("JAVA THREAD", "PRE EXECUTE: " + msg.arg1);

else if (msg.what == PROGRESS_UPDATE)
Log.d("JAVA THREAD", "PROGRESS: " + msg.arg1);

else if (msg.what == POST_EXECUTE)
Log.d("JAVA THREAD", "POST EXECUTE: " + msg.arg1);

super.handleMessage(msg);
}
};
new Thread() {
public void run() {
// send a PRE-EXECUTE message just for example
Message msg = Message.obtain();
msg.what = PRE_EXECUTE;
msg.arg1 = 9999;
handler.sendMessage(msg);

for (int i = 0; i < 10; i++) {
android.os.SystemClock.sleep(1000);
msg = Message.obtain();
msg.what = PROGRESS_UPDATE;
msg.arg1 = i;
handler.sendMessage(msg);
Log.d("JAVA THREAD", "ITERATION: " + i);
}

// send message to UI with result so it can update widgets, etc
msg = Message.obtain();
msg.what = POST_EXECUTE;
msg.arg1 = 12345;
handler.sendMessage(msg);

};
}.start();
}


AsyncTask
Android utility class to do background processing. Can override methods such as onPreExecute(), doInBackground(), onProgressUpdate(), and onPostExecute() to easily handle background processing and communicating things back to the UI thread for screen updates, etc. This is VERY handy and does all the home-grown handler stuff you'd have to do if you just used Java threads. Almost all of the methods you override run on the UI thread EXCEPT for doInBackground(). Android handles everything for you. The issue with this approach is that the task is tied to whatever started it -- if Android kills the process that started it, then the thread will die, too.


import android.os.AsyncTask;
private void testAsyncTask() {
// Create an AsyncTask that counts to 10 seconds and publishes updates
new AsyncTask() {
@Override
protected void onPreExecute() {
Log.d("TASK", "PRE EXECUTE ON UI THREAD");
super.onPreExecute();
}

@Override
protected Integer doInBackground(Void... params) {
for (int i = 0; i < 10; i++) {
android.os.SystemClock.sleep(1000);
publishProgress(i);
}
return 12345;
}

protected void onProgressUpdate(Integer[] values) {
Log.d("TASK", "PROGRESS: " + values[0]);
};

protected void onPostExecute(Integer result) {
Log.d("TASK", "POST EXECUTE ON UI THREAD: " + result);
};
}.execute();
}


Services
The recommended way to do anything that is long-running (more than a few seconds, perhaps, such as a background refresh with a web server), is to use a Service. A Service should be well-behaved -- it should not consume battery or memory resources, should not keep the device awake, etc. Services can still be killed be the OS, but the chances are less than other elements as Android gives a higher priority to services. There are still considerations you need to consider -- what if multiple requests to start a service are received? What if the device goes to sleep?

There are two variations of a Service in Android that I know about -- Service and IntentService. Service is mostly used for parallel processing as well as a permanent background process. I've always used the Service class incorrectly and ended up doing a lot of synchronization logic to ensure only a single thread was executing at one time.

And then I found the IntentService. The IntentService ensures that only a single thread is executing at a time. It also ensures that the service is stopped when work has completed (need to verify this!)

CommonsWare (http://commonsware.com) has released a really nice implementation of WakefulIntentService which makes it a snap to do work while retaining a WakeLock on the device so the CPU is available. Get it here: https://github.com/commonsguy/cwac-wakeful (Thanks, Mark -- I make it a point to read all of your posts as I'm bound to learn something new!)

BroadcastReceivers/Alarms
Of course, the biggest building block/foundation of Android is the use of Intents and BroadcastReceivers. The thing to consider when processing an intent in onReceive(), is that the CPU might not be available upon exiting from the onReceive() method. Therefore, if you are staring a background Thread or AsyncTask, you may be hosed! You will need to acquire a wake lock yourself, or, better yet, start a background service that does the processing and then shuts down. Check out the WakefulIntentService as it makes things really easy:


import com.commonsware.cwac.wakeful.WakefulIntentService;

public class MyTestService extends WakefulIntentService {

public MyTestService(String name) {
super(name);
}

@Override
protected void doWakefulWork(Intent intent) {
try {
// do something
} catch (Exception e) {
Log.e(C.TAG, "Evil.", e);
}
}
}


and, to start the service from anywhere in your code:


WakefulIntentService.sendWakefulWork(context, MyTestService.class);

2 comments:

Vincent Randal said...

Hello Dustin. That's a very nice summary about Android background processing. I am modifying some terminal emulator code to send some data to a plotter activity. Using logcat I have verified that the terminal emulator is continuing to run in the background using threads and handlers. What do you think would be my best option for transfer data from the terminal emulator thread/handler to my plotting activity?

Vincent Randal
Longmont, CO

Dustin said...

If the terminal emulator is running in the same process, then using a Handler is the way to go to post messages into a queue back to another thread. If they are in different processes, then there are various other possible options -- Content Provider, domain socket (low-level), or even using a BroadcastReceiver (heavy). The handler or content provider is the way to go.