mattias | niklewski.com

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.

Thread Looper Handler

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.

  1. android.os.MessageQueue
  2. android.os.Message
  3. android.os.Handler
  4. 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.