November 22, 2016

Android Message Handling Mechanism

I've been working on a performance monitor library for some time. While I'm implementing the looper-monitor feature, I found it's necessary to get clear on the Android message handling mechanism. In this article, I'll write about the relationship of Looper, Handler and Message. As it's just a memo for me, I'm not gonna write in every detail.

Overview

I write a rough draft to demonstrate how they interact with each other.
Draft

ActivityThread

Take the main thread for example, the real entry point of an application is ActivityThread.main(String[] args), so I'll start with this method.

public static void main(String[] args) {
	SamplingProfilerIntegration.start();

	// CloseGuard defaults to true and can be quite spammy.  We
	// disable it here, but selectively enable it later (via
	// StrictMode) on debug builds, but using DropBox, not logs.
	CloseGuard.setEnabled(false);

	Environment.initForCurrentUser();

	// Set the reporter for event logging in libcore
	EventLogger.setReporter(new EventLoggingReporter());

	Security.addProvider(new AndroidKeyStoreProvider());

	Process.setArgV0("<pre-initialized>");

	Looper.prepareMainLooper();

	ActivityThread thread = new ActivityThread();
	thread.attach(false);

	if (sMainThreadHandler == null) {
		sMainThreadHandler = thread.getHandler();
	}

	AsyncTask.init();

	if (false) {
		Looper.myLooper().setMessageLogging(new
				LogPrinter(Log.DEBUG, "ActivityThread"));
	}

	Looper.loop();

	throw new RuntimeException("Main thread loop unexpectedly exited");
}

Just ignore the irrelevant part, focus on these two lines:

Looper.prepareMainLooper();
Looper.loop();

ActivityThread init a looper and run into message loop. Looper.prepareMainLooper() is a wrapper method for Looper.prepare(boolean quitAllowed), it init a Looper instance, note that this instance is a thread-local value.

Looper

private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {
		throw new RuntimeException("Only one Looper may be created per thread");
	}
	sThreadLocal.set(new Looper(quitAllowed));
}

The code of Looper.loop().

public static void loop() {
	final Looper me = myLooper();
	if (me == null) {
		throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	}
	final MessageQueue queue = me.mQueue;

	// Make sure the identity of this thread is that of the local process,
	// and keep track of what that identity token actually is.
	Binder.clearCallingIdentity();
	final long ident = Binder.clearCallingIdentity();

	for (;;) {
		Message msg = queue.next(); // might block
		if (msg == null) {
			// No message indicates that the message queue is quitting.
			return;
		}

		// This must be in a local variable, in case a UI event sets the logger
		Printer logging = me.mLogging;
		if (logging != null) {
			logging.println(">>>>> Dispatching to " + msg.target + " " +
					msg.callback + ": " + msg.what);
		}

		msg.target.dispatchMessage(msg);

		if (logging != null) {
			logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
		}

		// Make sure that during the course of dispatching the
		// identity of the thread wasn't corrupted.
		final long newIdent = Binder.clearCallingIdentity();
		if (ident != newIdent) {
			Log.wtf(TAG, "Thread identity changed from 0x"
					+ Long.toHexString(ident) + " to 0x"
					+ Long.toHexString(newIdent) + " while dispatching to "
					+ msg.target.getClass().getName() + " "
					+ msg.callback + " what=" + msg.what);
		}

		msg.recycle();
	}
}

Looper.loop() takes messages from message queue repeatedly, and dispatch them to their target. The Message.target is a Handler, this field gets its value when we call Handler.sendMessage(Message msg). When we call this method, it eventually get here.

Handler and Message

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this;
	if (mAsynchronous) {
		msg.setAsynchronous(true);
	}
	return queue.enqueueMessage(msg, uptimeMillis);
}

A Handler instance holds references to a looper and the looper's message queue. When we send a message with a handler, in fact we inject the message to the looper's message queue associated with the handler. When Looper.loop() take the message out, this message will be handled by Handler.dispatchMessage(Message msg).

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		handleCallback(msg);
	} else {
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
				return;
			}
		}
		handleMessage(msg);
	}
}

The code is pretty simple. mCallback is assigned in the handler's constructor Handler(Callback callback, boolean async), it's an interface:

public interface Callback {
	public boolean handleMessage(Message msg);
}

That's why we have to implement the Callback interface or override Handler.handleMessage(Message msg) method to handle the message.
Message.callback field is a Runnable instance. When we call Handler.post(Runnable r), the Runnable instance is assigned to Message.callback, and gets handled by Handler.handleCallback(Message message).

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
	Message m = Message.obtain();
	m.callback = r;
	return m;
}
private static void handleCallback(Message message) {
	message.callback.run();
}

By the way, life cycle of Android application is also based on Handler. An inner class ActivityThread.H receives message from AMS and call Activity's life cycle callback accordingly.