Android: Nuts and Bolts V

This episode of Nuts and Bolts puts us back on course with what I had originally planned as N&B 3 – “Threads, or, How to Not Blow Up Your New User Interface”. We’ve already discussed how to make a UI, and we’ve talked about how to make it work. The next logical step is to learn how to make our application do something meaningful. For simple applications this may not mean using threads, but any time your app needs to A. sit around and wait for something, or B. do intensive computations, you will need Threads. Become an Android sewing master by clicking the “Read More“!

 

First, I should probably explain precisely why you need to care about using a Thread to do intense computations. The way that User Interfaces ‘work’ (i.e. buttons click, text boxes take text, etc) is through the process of a ‘UI Thread’, or ‘Parent Thread’ in our case, constantly and passively checks for input and makes necessary graphical changes (that is what Listeners are for!). Now if we were to, for example, stick a sleep(30000); inside of our onCreate() method, our UI would do one of two things: if we put the bad statement as the very first line the UI wouldn’t draw for 30 seconds, if we put it after the super.onCreate(savedInstanceState); and setContentView(R.layout.main); stuff, the UI would appear unresponsive for 30 seconds. Either way, this is not a good thing.

Now, instead of sleeping for 30 seconds, pretend I said “download some information from the web” or “calculate a complex trajectory” or something equally time consuming. These are reasonable things to want to do on a cell phone (okay, trajectory is a little questionable…), but if you try to do them in your UI thread, you’ll have the same terrible results. This is where we use a ‘worker thread’! We create a worker so that the cell phone’s processor can multi-task between keeping the UI active and performing our web download.

There are a number of approaches to adding threads, I will cover two of these: ‘the quick way’, and ‘the better way’. First, the quick way: we create a class that extends Thread, fill it with whatever code we want, create a new instance of that class, and call .start() on it. Credit for the class extension idea goes to learningandroid.org (who seem to be having some server issues at the moment, but I’m linking nonetheless…)

Invoke the following with:
ThreadExt thread1 = new ThreadExt();
thread1.start();

public class ThreadExt extends Thread{
public void run(){
//You are NOT allowed to change UI elements
//from this thread. You can query them for
//values, but not alter the values!

//Perform time consuming actions.
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Let the handler know that you're done.   
handler.sendEmptyMessage(0);
}
};

private Handler handler = new Handler() {
public void handleMessage(Message msg) {
//Update the UI thread with any code you like
//You ARE allowed to do TextView.setText() or other UI
//changes here, but NOT in ThreadExt!!!
}
};

This is a simple way to handle creation of a Worker Thread, and a perfectly fine choice if you don’t need to do anything more complex. The reason we write an extension to Thread instead of overriding it, is that we may want a second worker thread in the future. In that case, you can’t override it twice! In this way, we have a different class to instantiate for each different kind of work we need. Handler.sendEmptyMessage invokes the handler, which is done after all of the work is finished. The Handler resides in the UI thread and sits there listening to the worker thread until it finishes. It can take values from the worker and update them to the UI.

The ‘better way’ to do threads, in my humble opinion, is to use AsyncTask(). You may recall that I mentioned this class in an earlier post. This class is a bit more complex, but you get a lot more cool stuff from it too. In the above example, what would you do if I asked you to update the UI thread twice from your worker? There may exist some way to do this, but I couldn’t figure it out, and AsyncTask makes it easy:

Invoke the following with:
new DownloadResults().execute("String");

private class DownloadResults extends AsyncTask {
@Override
protected void onPreExecute(){
//You can do preparatory work here, as in this showDialog command (you can touch the UI here)
showDialog(ID_DIALOG_ANNOTATE);
}

@Override
protected Integer doInBackground(String... params) {
//Here, you do meaningful work, and then update as to your progress.
// (you cannot touch the UI here!)

Integer status = 0;
for(int i=0; i //
// Do things
//
status++;
publishProgress(status);
}           
return status;
}

@Override
protected void onProgressUpdate(Integer... values) {
// Update your progress bar, or whatever needs updating (you can touch the UI here)
mProgressBar.setProgress(values[0]);
}

@Override
protected void onPostExecute(Integer value) {
// Conclude progress dialogues etc...
// (You can touch the UI here)
}

}

Note that in both examples, I’m nagging you about where you can and where you cannot mess with your UI. This is important and caused me a few Force-Quits; I’m a dumb dumb. Basically, with either of these approaches you have a piece which is split off in the worker thread to do your time consuming stuff, and ‘the rest’ actually resides in your UI thread to pick up any changes. This model is a bit difficult to pick up on at first, but once you get it, it is not at all limiting.

I glossed over the progress bar stuff because, to be honest, it is somewhat complex by itself. I’ll have to cover that one in a separate N&B article. For now, just ignore that stuff and focus on the threads, because that is ultimately our focus here.

One last little gotcha that users should be aware of is that orientation changes mid-progress dialog will destroy and re-create the progress dialog. This does terrible and strange things to Android, and I don’t know how to fix it. I’m under the impression that at this point in time this is a known Android issue and not my lack of knowledge, but I could be wrong!

Once again, thanks for reading, don’t worry if you’re confused at this point as this represents weeks of agony on my part! This is here more for reference material than anything else, so I hope it helps! 🙂