The Android Event Loop
Android apps are event driven, and this post explores the nitty-gritty of the event loop. It is not about concurrent programming. In fact, perhaps the biggest strength of an event loop is that it avoids concurrency altogether.
The documentation is sparse around the android.os
package so we'll need to
visit the frameworks/base github repo as we go along.
At the heart of an event driven framework is the event loop. In Win32 it is explicit and part of your program. In Android or Java/Swing it is implicit and burrowed inside the framework. In either case, it has the following four components:
A thread that drives the loop
In Android this is your app's main thread. It has the special privilege of being allowed to modify the view hierarchy. It also has the dubious privilege of causing ANRs if you block it from processing input events.
A message queue
MessageQueue holds events from when they are posted until they can be
dispatched (handled). Message represents an event. The message type is
determined by the curiously named field 'what
'. There are some optional
parameters, whose values depend on the message type. You can define and send
your own messages, like this:
static final int MSG_HELLO_WORLD = 1; // a custom message type
...
Message msg = Message.obtain();
msg.setTarget(myHandler); // target is explained below
msg.what = MSG_HELLO_WORLD;
msg.sendToTarget();
The framework uses the message queue to tell your main thread about everything
from activity lifecycle events to drawing events. Once these events hit your
application code, they have been converted to convenient callbacks. If you
look around in the android.app
package you will see how this is done. For
instance, here is the message that will eventually result in a
call to Activity.onNewIntent()
. Put a break point there and you might see
this callstack.
...
Handler.handleMessage(msg)
ActivityThread.handleNewIntent(data)
ActivityThread.performNewIntents(token, intents)
ActivityThread.deliverNewIntents(r, intents)
Instrumentation.callActivityNewIntent(activity, intent)
YourActivity.onNewIntent(intent)
A message handler
This is usually implemented as a plain switch/case on the message type. In
Android you extend the Handler class and implement
handleMessage(Message)
. Here is a handler for the message we just sent.
class HelloWorldHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_HELLO_WORLD:
Log.d(TAG, "Hello world!");
break;
...
}
}
Instead of a type and params, a Message
can contain a Runnable
object. In this case "handling" the message means executing the
runnable, and handleMessage()
is not invoked. This is how
Activity.runOnUiThread()
is implemented.
There can be more than one Handler
per MessageQueue
. This may seem odd,
but it is a convenient way to allow system messages and user-defined messages
to coexist in the main thread's event loop. The way this works is that each
Message
has a reference to its target -- the Handler
that will
eventually receive it. That's the reason for the setTarget
call in the
previous example.
Since the target is used to route a message to its handler, the message type is only used within the scope of that handler. So when you create your own handler you are free to define your message type constants without worrying about collisions with other handlers. In contrast, Win32 defines a user range of messages, to avoid collisions between system messages and user messages.
The loop
This is the piece of code that fetches incoming messages and hands them over to the Handler one at a time. In pseudo code a traditional message loop looks something like this:
while message = queue.getNextMessage():
dispatch(message)
Except in Android, due to the support for multiple handlers, it is really more like this:
while message = queue.getNextMessage():
message.getTarget().dispatchMessage(message)
Because Android's event loop is part of the framework, you don't write this
code yourself. Looper has a method loop()
that does it for you.
It is very rare to interact with a Looper instance from app code, but you
might recognize it from near the bottom of a stack trace after an ANR or from
the debugger.
The four classes we've seen so far are all there is to it, really.
android.os.MessageQueue
android.os.Message
android.os.Handler
android.os.Looper
There is not even a lot of code in them. Now that you know their purpose, let's find out how they fit together.
A complete example
Here is a minimal example just to highlight a few points. Pretend we have a C
library with a method fetchInBackground()
. This method is asynchronous,
which means it returns right away. Presumably the C library has an internal
thread that does the background work. Maybe over the network. We don't really
care. Some time later this thread delivers a result by calling
onResult(Object)
. We need to display the result on screen, but Android
forbids all threads but the main thread from modifying the view hierarchy, so
the callback must be routed over to the main thread. One way to do this is
with a Handler.
class MyLibraryWrapper {
private static final int COMPUTATION_DONE = 1;
private Handler myHandler = new MyHandler();
private class MyHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case COMPUTATION_DONE:
updateViewWithResult(msg.obj);
break;
}
}
};
/**
* Fetches some magic value in the background. Calls
* onResult() to deliver the value.
*/
public native void fetchInBackground();
/**
* Delivers a result. This is called on a background
* thread that is managed by the native library.
*/
public void onResult(Object result) {
Message msg = Message.obtain();
msg.setTarget(myHandler);
msg.what = COMPUTATION_DONE;
msg.obj = result;
msg.sendToTarget();
}
...
}
One thing that may not be obvious is how the message from onResult()
ends up
on the main thread. Note that we never refer to the main thread explicitly. In
fact we never mention any threads at all. So how does the message find its way
to the correct message queue?
To answer that question we need to peek inside Handler()
.
public Handler() {
...
mLooper = Looper.myLooper();
...
mQueue = mLooper.mQueue;
mCallback = null;
}
No references to the main thread here either, but it fetches a looper called 'myLooper'. What looper is that?
Here is the relevant part of the Looper class.
public class Looper {
static final ThreadLocal<Looper> sThreadLocal =
new ThreadLocal<Looper>();
final MessageQueue mQueue;
private static void prepare(...) {
...
sThreadLocal.set(new Looper(...));
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
...
}
Calling prepare()
will deposit a new Looper in sThreadLocal
, and that is
what myLooper()
returns.
So any thread that wants a message loop must call Looper.prepare()
once, to populate sThreadLocal
with a new Looper instance. For the main thread, this happens very early in the app's life cycle. From then on, any Handler
instance created on a thread is tied to that thread's Looper instance.
It is fairly uncommon for user-created threads to need a message loop. Still,
Android provides a Thread subclass called HandlerThread for that purpose.
It has a run method that sets up a Looper with prepare()
and starts the
message loop.
Returning to the example, what thread is Handler()
invoked from? Although I did not show where
MyLibraryWrapper was instantiated, you can assume it was on the main thread.
That means when Handler()
runs, it receives the main thread's looper and
message queue.
To send the message, we call Message.sendToTarget()
. The message knows
nothing about queues or loopers, it just delegates the call to its target
handler:
class Message {
...
public void sendToTarget() {
target.sendMessage(this);
}
Here's a snapshot of the call stack that shows the enqueue operation in all its delegated glory.
<Library Managed Thread>
MyLibraryWrapper.onResult()
Message.sendToTarget()
Handler.sendMessage(msg)
Handler.sendMessageDelayed(msg, 0)
Handler.sendMessageAtTime(msg, <now>)
MessageQueue.enqueueMessage(msg, <now>)
Now the message is in the queue. Some time later, when the main thread is ready, it dequeues and dispatches the message. Here is what that call stack looks like:
<Main Thread>
ActivityThread.main()
Looper.loop()
/* msg = MessageQueue.next() */
Handler.dispatchMessage(msg)
MyHandler.handleMessage(msg)
Now that we have seen how to use Android's event loop, it's time to abuse it. Next time I will show you how to bend Looper to your will, which is of great use when testing event-driven code.