Understanding Android Looper, MessageQueue, and Native Message Handling
This article explains how Android drives applications through a message‑driven model by detailing the roles of Message, MessageQueue, Looper, Handler, and ThreadLocal, and walks through the native implementation of ActivityThread's main loop, including prepareMainLooper, MessageQueue initialization, epoll integration, and the polling mechanisms that power the message processing cycle.
Android applications are driven by a message‑driven model where each app maintains a MessageQueue and the main thread repeatedly retrieves messages via a Looper and processes them with a Handler . The article first defines the core concepts:
Message : represents an action (what) or a Runnable , always associated with a target Handler .
MessageQueue : a linked‑list based queue that stores Message objects and provides insertion and removal operations.
Looper : continuously extracts Message objects from the MessageQueue and dispatches them to their target Handler .
Handler : the actual processor of messages, capable of sending, receiving, and removing messages.
ThreadLocal : provides thread‑local storage to isolate data per thread.
The article then shows the Java implementation of ActivityThread.main() , which prepares the main looper, creates the ActivityThread instance, and starts the infinite loop:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("
");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}prepareMainLooper() simply calls prepare(false) and stores the created looper in sMainLooper :
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}The prepare(boolean quitAllowed) method creates a Looper instance and saves it in a ThreadLocal variable:
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));
}During looper creation a MessageQueue is also instantiated. Its constructor delegates the real work to a native method nativeInit() :
public class MessageQueue {
private final boolean mQuitAllowed;
private int mPtr; // used by native code
private native void nativeInit();
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
}The native side allocates a NativeMessageQueue object and returns its pointer as a jlong :
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast
(nativeMessageQueue);
}The native NativeMessageQueue creates its own Looper object, which mirrors the Java Looper :
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}The native Looper sets up an epoll instance to wait for events. Its pollOnce() method delegates to pollInner() after handling any pending responses:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
if (response.request.ident >= 0) {
if (outFd) *outFd = response.request.fd;
if (outEvents) *outEvents = response.events;
if (outData) *outData = response.request.data;
return response.request.ident;
}
}
if (result != 0) return result;
result = pollInner(timeoutMillis);
}
}The inner polling logic adjusts the timeout based on the next message's scheduled time, then calls epoll_wait() and processes the returned events, including waking the looper, handling idle handlers, and invoking callbacks:
int Looper::pollInner(int timeoutMillis) {
// Adjust timeout for next message
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0 && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
}
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mPolling = false;
mLock.lock();
if (eventCount < 0) {
if (errno == EINTR) goto Done;
ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
result = POLL_ERROR;
goto Done;
}
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {
if (epollEvents & EPOLLIN) awoken();
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
}
}
}
Done:
// Invoke pending message callbacks and response callbacks (omitted for brevity)
mLock.unlock();
return result;
}Overall, the article demonstrates how the Java‑level message loop is backed by native C/C++ structures, how Looper.prepareMainLooper() creates the main looper and its queue, and how the native Looper uses epoll to efficiently wait for and dispatch messages, forming the core of Android's event‑driven execution model.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.