Skip to content Skip to sidebar Skip to footer

ACQUIRING DATA

The Android operation system has exactly one blessed thread authorized to change
anything that will be seen by the user. This alleviates what could be a concurrency
nightmare, such as view locations and data changing in one thread while a different
one is trying to lay them out onscreen. If only one thread is allowed to touch
the user interface, Android can guarantee that nothing vital is changed while it’s
measuring views and rendering them to the screen. This has, unfortunately, serious
repercussions for how you’ll need to acquire and process data. Let me start
with a simple example.
YOU THERE, FETCH ME THAT DATA!
Were I to ask you, right now, to download an image and display it to the screen,
you’d probably write code that looks a lot like this:
public void onCreate(Bundle extra){
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 bitmap = BitmapFactory.decodeStream(is);
ImageView iv = (ImageView)findViewById(R.id.main_image);
if(iv!=null)
iv.setImageBitmap(bitmap);
}catch(Exception e){
Log.e(“ImageFetching”,”Didn’t work!”,e);
}
}

This is exactly what I did when initially faced with the same problem. While
this code will fetch and display the required bitmap, there is a very sinister issue
lurking in the code—namely, the code itself is running on the main thread. Why
is this a problem? Consider that there can be only one main thread and that the
main thread is the only one that can interact with the screen in any capacity. This
means that while the example code is waiting for the network to come back with
image data, nothing whatsoever can be rendered to the screen. This image-fetching
code will block any action from taking place anywhere on the device. If you hold
the main thread hostage, buttons will not be processed, phone calls cannot be
answered, and nothing can be drawn to the screen until you release it.

WATCHDOGS

Given that a simple programmer error (like the one in the example code) could
effectively cripple any Android device, Google has gone to great lengths to make
sure no single application can control the main thread for any length of time. Hogging
too much of the main thread’s time will result in this disastrous dialog screen
(Figure 4.1) showing up over your application.

This dialog is unaffectionately referred to by developers as an ANR (App Not
Responding) crash. Although operations will continue in the background, and
the user can press the Wait button to return to whatever’s going on within your
application, this is catastrophic for most users, and you should avoid it at all costs.
WHAT NOT TO DO
What kind of things should you avoid on the main thread?
Anything involving the network
Any task requiring a read or write from or to the file system
Heavy processing of any kind (such as image or movie modification)
Any task blocking a thread while you wait for something to complete
Excluding this list, there isn’t much left, so, as a general rule, if it doesn’t involve
setup or modification of the user interface, don’t do it on the main thread.

WHEN AM I ON THE MAIN THREAD?

Anytime a method is called from the system (unless explicitly otherwise stated), you
can be sure you’re on the main thread. Again, as a general rule, if you’re not in a thread
created by you, it’s safe to assume you’re probably on the main one, so be careful.

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.
p png”);
HttpURLConnection httpCon = (HttpURLConnection)
p 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.

Post a Comment for "ACQUIRING DATA"