In Chapter 2, Staying Responsive with AsyncTask, we familiarized ourselves with the most well-known concurrency construct of the Android platform. What is perhaps less well known are the mechanics of how AsyncTask
coordinates work between background threads and the main thread.
In this chapter we'll meet some of the lower-level constructs that AsyncTask
builds on to get its work done.
We'll see how to defer tasks to happen in the future on the main thread, whether that is as soon as possible, after a specified delay, or at a specified time, and we'll apply the same concepts to scheduling work on background threads and coordinating the results with the main thread.
In this chapter we will cover the following topics:
Looper
Handler
post
Runnable
send
Handler
HandlerThread
Handler
programming issuesHandler
and HandlerThread
Before we can understand Handler
, we need to meet the aptly named Looper
. Looper
is a simple class that quite literally loops forever, waiting for Messages to be added to its queue and dispatching them to target Handlers. It is an implementation of a common UI programming concept known an Event Loop.
To set up a Looper
thread, we need to invoke two static methods of Looper
—prepare
and loop
—from within the thread that will handle the message loop.
class SimpleLooper extends Thread { public void run() { Looper.prepare(); Looper.loop(); } }
That was easy; however, the SimpleLooper
class defined here provides no way to add messages to its queue, which is where Handler
comes in.
Handler
serves two purposes—to provide an interface to submit Messages to its Looper
queue and to implement the callback for processing those Messages when they are dispatched by the Looper.
To attach a Handler to SimpleLooper
, we need to instantiate the Handler
from within the same thread that prepared the Looper. The Handler is associated with the main Looper
by the super-class constructor, which obtains the Looper using a static method.
class SimpleLooper extends Thread { public Handler handler; public void run() { Looper.prepare(); handler = new Handler(); Looper.loop(); } }
Once started, the Looper
thread will wait (Object.wait
) inside Looper.loop()
for Messages to be added to its queue.
When another thread adds a Message
to the queue it will notify (Object.notify
) the waiting thread, which will then dispatch the Message
to its target Handler
by invoking the Handler's
dispatchMessage
method.
The wait/notify mechanism is a very efficient way of suspending activity on a particular thread while there is no work for it to do, so that it doesn't waste CPU cycles, then resuming work once there is something for it to do.
Because dispatchMessage
is invoked by the thread running the message loop, we can send Messages to the Handler
from any thread and they will always be dispatched by the message loop thread as shown in the following diagram:
We already saw that we can create our own Looper
threads, but here's the fun part and this may come as a surprise: the main thread is in fact a Looper
thread, as we can see in the following stack trace:
at example.handler.MyActivity.onCreate(Example1Activity.java:19)
at android.app.Activity.performCreate(Activity.java:5206)
…
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4898)
That's right! Everything that happens in an Activity
lifecycle callback method is running in a dispatchMessage
call invoked by the main Looper
!
The interesting thing to realize here is that we can send messages to the main thread from any other thread (or even from the main thread itself) and in doing so, hand over work from background threads to the main thread—for example, to have it update the user interface with the results of background processing.
3.15.29.146