In my career as a software engineer, I have encountered plenty of example code. As a Google Developer Advocate for Android, part of my job involves creating example code for illustrating certain concepts and APIs.
Most of the time, people will take a snippet of example code and bend it to their will until it no longer resembles the original. But that's the whole point: the sample code has served its purpose. It has illustrated some key concept and hopefully helped someone build some great software.
Last week I met with a developer who was working on a prototype application for a potential client of his. In the course of our discussion, he told me that he has been continuing to use some example code I provided him with, as-is, without modification. This of course made me very happy, but more importantly, made me think that perhaps this code might also be useful to other Android developers out there.
WARNING: This is about to get a bit technical, if you or your children grow faint at the sight of code, please stop reading now.
The tricky problem of multithreaded programming with a Graphical User Interface (GUI)
Every GUI widget framework I have ever encountered has been single-threaded (usually called "the main thread" or "the gui thread.") Learning how best to use other threads and have them "play nicely" with your widget framework of choice can be tricky. The main problem with most widget frameworks (including Android) is that you can't call methods on widget objects if your code is running in a non-GUI thread.
Horrible crimes against code that I have committed in the past
Way back in the 90's I was wrestling with the Swing UI framework. I was trying to write applications that used a "background" thread to do some long running thing. This included some horribly hacky things, like putting Thread.sleep() statements in my code at certain places until it kinda-sorta worked (the really frustrating thing about calling UI methods from non-GUI threads is that sometimes it does actually work!)
That was until I learned about
javax.swing.SwingUtilities.invokeLater(Runnable thingToDo)
You can call the
invokeLater()
method from any non-GUI thread and it will invoke your
thingToDo.run()
method in the context of the UI thread, which means that your Runnable object can do things like update a progress bar etc.
In the Android framework, the
android.os.Handler
class does the same thing. The way you use it is slightly different, though. If you create an android.os.Handler object in the context of the GUI thread (in the onCreate() method of an activity, for example) then you can use that handler instance in the context of another thread in order to do your UI work.
My little code patternI decided to reuse a code pattern that I developed years ago for doing multithreaded GUI programming, and here it is:
public interface GUITask {
void executeNonGuiTask() throws Exception;
void after_execute();
void onFailure(Throwable t);
}
The basic idea behind this is that
executeNonGuiTask() is invoked from a non-GUI thread. If it returns without throwing an exception, the
after_execute() method will get called
from the GUI thread (this is important). If, on the other hand, executeNonGuiTask() throws an exception, that exception will be passed to the onFailure() method, again in the context
of the GUI thread.
Sometimes you need to do something that might take more than a handful of milliseconds (download a file, talk to a web service, compute the
mahalanobis distance between data points etc.) If that something takes a human-noticable period of time, you'll want to do that activity on a non-GUI thread in order to keep the UI responsive. That something will either complete successfully, or it will fail for some reason (IOException on a network thread, trying to invert a singular matrix, etc.) If it completes, you will most likely want the end user to know about this, so you will probably want to do something with the UI. If it fails for some reason, you will also
probably want to affect the UI (even if it is to display a message to "try again later"), which is why after_execute() and onFailure() are always invoked in the context of the UI thread.
All you need is one threadAnother thing I have found in my own forays into multithreaded UI programs, is that you typically only need one non-UI thread to do your background processing. One technique which
has worked well for me is to have a "task queue" which is essentially just one thread that I can "feed" Runnable objects to. If I "feed" 3 Runnable objects to my task queue, that task queue
will execute the first, then the second and then the third Runnable object that has been "queued up".
Here's the code:
public class TaskQueue {
private LinkedList<Runnabl> tasks;
private Thread thread;
private boolean running;
private Runnable internalRunnable;
private class InternalRunnable implements Runnable {
public void run() {
internalRun();
}
}
public TaskQueue() {
tasks = new LinkedList«Runnable»();
internalRunnable = new InternalRunnable();
}
public void start() {
if (!running) {
thread = new Thread(internalRunnable);
thread.setDaemon(true);
running = true;
thread.start();
}
}
public void stop() {
running = false;
}
public void addTask(Runnable task) {
synchronized(tasks) {
tasks.addFirst(task);
tasks.notify(); // notify any waiting threads
}
}
private Runnable getNextTask() {
synchronized(tasks) {
if (tasks.isEmpty()) {
try {
tasks.wait();
} catch (InterruptedException e) {
Log.e("androidx", "Task interrupted", e);
stop();
}
}
return tasks.removeLast();
}
}
private void internalRun() {
while(running) {
Runnable task = getNextTask();
try {
task.run();
} catch (Throwable t) {
Log.e("androidx", "Task threw an exception", t);
}
}
}
}
This code is entirely generic Java code except for the use of android.util.Log (which could easily be removed). To use it, just create a new TaskQueue, call the start() method,
then "feed" it Runnable objects by calling taskQueue.addTask(myTask); when finished, just call taskQueue.stop();
Back to my little patternThe TaskQueue class above is really just meant to be a building block, a piece of code that can be used by other pieces of code. Here is the code that bridges the gap between the TaskQueue class above, the android UI framework and the GUITask interface I described earlier:
import android.os.Handler;
import android.os.Message;
import androidx.LogX;
public class GUITaskQueue {
private static final int HANDLE_EXCEPTION = 0x1337;
private static final int HANDLE_AFTER_EXECUTE = 0x1338;
private TaskQueue taskQ;
private Handler handler;
private static GUITaskQueue singleton;
public static GUITaskQueue getInstance() {
if (singleton == null) {
singleton = new GUITaskQueue();
singleton.start();
}
return singleton;
}
private GUITaskQueue() {
taskQ = new TaskQueue();
handler = new MyHandler();
}
public void start() {
taskQ.start();
}
public void stop() {
taskQ.stop();
}
public void addTask(GUITask task) {
taskQ.addTask(new GUITaskAdapter(task));
}
/**
* Adds a task with an associated progress indicator. The indicator's showProgressIndicator() gets
* called immediately then the hideProgressIndicator() gets called before the GUITask's
* handle_exception() or after_execute() method gets called.
*/
public void addTask(ProgressIndicator progressIndicator, GUITask task) {
if (progressIndicator == null) {
addTask(task);
} else {
addTask(new GUITaskWithProgress(task, progressIndicator));
}
}
private static class GUITaskWithProgress implements GUITask {
private GUITask delegate;
private ProgressIndicator progressIndicator;
GUITaskWithProgress(GUITask _delegate, ProgressIndicator _progressIndicator) {
delegate = _delegate;
progressIndicator = _progressIndicator;
progressIndicator.showProgressIndicator();
}
public void executeNonGuiTask() throws Exception {
delegate.executeNonGuiTask();
}
public void onFailure(Throwable t) {
progressIndicator.hideProgressIndicator();
delegate.onFailure(t);
}
public void after_execute() {
progressIndicator.hideProgressIndicator();
delegate.after_execute();
}
};
private static class GUITaskWithSomething {
GUITask guiTask;
T something;
GUITaskWithSomething(GUITask _guiTask, T _something) {
guiTask = _guiTask;
something = _something;
}
}
private void postMessage(int what, Object thingToPost) {
Message msg = new Message();
msg.obj = thingToPost;
msg.what = what;
handler.sendMessage(msg);
}
private void postException(GUITask task, Throwable t) {
postMessage(HANDLE_EXCEPTION, new GUITaskWithSomething(task, t));
}
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_EXCEPTION:
GUITaskWithSomething thingie = (GUITaskWithSomething) msg.obj;
thingie.guiTask.onFailure(thingie.something);
break;
case HANDLE_AFTER_EXECUTE:
GUITask task = (GUITask) msg.obj;
try {
task.after_execute();
} catch (Throwable t) {
LogX.e(t);
}
break;
}
super.handleMessage(msg);
}
}
private class GUITaskAdapter implements Runnable {
private GUITask task;
GUITaskAdapter(GUITask _task) {
task = _task;
}
public void run() {
try {
task.executeNonGuiTask();
postMessage(HANDLE_AFTER_EXECUTE, task);
} catch (Throwable t) {
postException(task, t);
}
}
}
}
To use the above code, just call GUITaskQueue.getInstance().addTask(myTask) where myTask is an object that implements GUITask.
Until next time...I hope this has been helpful for those of you interested in multithreaded programming on Android. Feel free to use any of the above code in any project you like, but please keep in mind that Google and I cannot be held responsible if your giant killer robot malfunctions because of a bug in the example code (also, if you have a giant killer robot, can I take it out for a spin?) As always, drop us a comment below with your questions, comments or finished code to share with the world.
Mike Jennings
Android Developer Advocate