跪拜 Guibai
← All articles
Android

Android's MessageQueue Is Not a Queue — It's a Time-Aware Message Selector

By 小明的运行时 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Misreading MessageQueue as a plain FIFO leads to wrong assumptions about ordering, delays, and thread idling. Understanding its selection rules — timestamps, barriers, and the native poll/wake boundary — is the prerequisite for diagnosing why a Handler callback didn't fire when expected.

Summary

A MessageQueue does more than store messages. It decides which message the Looper can retrieve next by checking each candidate's `when` timestamp, whether a sync barrier is blocking normal messages, and whether an async message can bypass that barrier. When no message is immediately executable, it calculates a timeout and calls nativePollOnce to put the thread into an efficient wait — not a busy spin — until a wake event, fd event, or timeout occurs.

postDelayed does not create a timer thread. It stamps a future `when` on the message and enqueues it; the Looper simply won't select it until that time arrives. A sync barrier is a special message with `target == null` that temporarily blocks synchronous messages behind it, allowing only async messages through — a mechanism ViewRootImpl uses to prioritize frame rendering.

When a message doesn't execute, the root cause is rarely "the queue is blocked." The real checklist starts earlier: was the post accepted, has the Looper quit, was the message removed, or is it simply not yet due? The return value of `handler.post()` — `true` or `false` — is the first diagnostic signal, not an afterthought.

Takeaways
MessageQueue selects the next message by earliest `when`, not by insertion order; a later-posted message with an earlier timestamp executes first.
postDelayed stamps a future `when` and enqueues normally; no dedicated timer thread is created.
nativePollOnce puts the Looper thread into an efficient wait (blocking on timeout, fd, or wake) instead of burning CPU in a busy loop.
nativeWake only wakes the sleeping Looper thread; the thread must re-enter MessageQueue.next() to actually retrieve the message.
A sync barrier is a message with `target == null` that blocks subsequent synchronous messages; async messages bypass it, which is how the UI traversal path prioritizes frame callbacks.
IdleHandler is not a low-priority task queue; it fires when the queue is temporarily idle and is unsuitable for heavy or latency-sensitive work.
After quit or quitSafely, new posts can return false because the queue rejects them; a false return means the message never entered the queue.
Diagnosing a missing callback starts with checking whether post returned true, not with examining handleMessage.
Conclusions

Many developers treat Handler.post as a fire-and-forget call, but the boolean return value is the earliest and most overlooked signal that a message was rejected due to Looper shutdown.

The distinction between Java-layer timeout calculation and native-layer waiting is a clean separation of concerns: the Java side knows message semantics, the native side knows how to sleep efficiently across multiple event sources.

Calling nativePollOnce does not guarantee the thread sleeps; a timeout of 0 causes an immediate return, which is used when IdleHandlers just ran and might have enqueued new work.

Async messages are widely misunderstood as a performance feature; they only change barrier-gating semantics, and converting business Handlers to async can silently reorder execution in unintended ways.

Concepts & terms
Sync Barrier
A special Message with target == null that, when encountered in the queue, prevents subsequent synchronous messages from being retrieved until the barrier is removed. Async messages can bypass it. Used internally by the UI framework to prioritize frame rendering.
nativePollOnce
A native method that puts the Looper thread into an efficient wait state. It can block on a timeout, a wake event, or registered file descriptor events. The timeout is calculated by the Java-layer MessageQueue based on the next due message.
IdleHandler
A callback interface invoked when the MessageQueue has no immediately executable messages. It is meant for lightweight, deferrable work and offers no scheduling guarantees; long-running IdleHandlers delay subsequent messages.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗