- The Main Thread
- Getting Off the Main Thread
- The AsyncTask
- The IntentService
- Wrapping Up
Getting Off the Main Thread
You can see why holding the main thread hostage while grabbing a silly picture of the Golden Gate Bridge is a bad idea. But how, you might be wondering, do I get off the main thread? An inventive hacker might simply move all the offending code into a separate thread. This imaginary hacker might produce code looking something like this:
public void onCreate(Bundle extra){ new Thread(){ public void run(){ try{ URL url = new URL("http://wanderingoak.net/bridge. png"); HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); if(httpCon.getResponseCode() != 200) throw new Exception("Failed to connect"); InputStream is = httpCon.getInputStream(); Bitmap bt = BitmapFactory.decodeStream(is); ImageView iv = (ImageView)findViewById(R.id.remote_image); iv.setImageBitmap(bt); }catch(Exception e){ //handle failure here } } }.start(); }
“There,” your enterprising hacker friend might say, “I’ve fixed your problem. The main thread can continue to run unimpeded by the silly PNG downloading code.” There is, however, another problem with this new code. If you run the method on your own emulator, you’ll see that it throws an exception and cannot display the image onscreen.
Why, you might now ask, is this new failure happening? Well, remember that the main thread is the only one allowed to make changes to the user interface? Calling setImageBitmap is very much in the realm of one of those changes and, thus, can be done only while on the main thread.
Getting Back to Main Land
Android provides, through the Activity class, a way to get back on the main thread as long as you have access to an activity. Let me fix the hacker’s code to do this correctly. As I don’t want to indent the code into the following page, I’ll continue this from the line on which the bitmap is created (remember, we’re still inside the Activity class, within the onCreate method, inside an inline thread declaration) (why do I hear the music from Inception playing in my head?).
For orientation purposes, I’ll continue this from the line on which the bitmap was created in the previous code listing. If you’re confused, check the sample code for this chapter.
final Bitmap bt = BitmapFactory.decodeStream(is); ImageActivity.this.runOnUiThread(new Runnable() { public void run() { ImageView iv = (ImageView)findViewById(R.id.remote_image); iv.setImageBitmap(bt); } }); //All the close brackets omitted to save space
Remember, we’re already running in a thread, so accessing just this will refer to the thread itself. I, on the other hand, need to invoke a method on the activity. Calling ImageActivity.this provides a pointer to the outer Activity class in which we’ve spun up this hacky code and will thus allow us to call runOnUiThread. Further, because I want to access the recently created bitmap in a different thread, I’ll need to make the bitmap declaration final or the compiler will get cranky with us.
When you call runOnUiThread, Android will schedule this work to be done as soon as the main thread is free from other tasks. Once back on the main thread, all the same “don’t be a hog” rules again apply.
There Must Be a Better Way!
If you’re looking at this jumbled, confusing, un-cancelable code and thinking to yourself, “Self. There must be a cleaner way to do this,” you’d be right. There are many ways to handle long-running tasks; I’ll show you what I think are the two most useful. One is the AsyncTask, a simple way to do an easy action within an activity. The other, IntentService, is more complicated but much better at handling repetitive work that can span multiple activities.