Skip to content

MessageQueue消息入队

从 Handler 入队落到 MessageQueue 链表,解释时间排序、同步屏障、异步消息与唤醒判定。

基于android-17.0.0_r1
AndroidMessageQueueHandler异步消息源码阅读

MessageQueue消息入队 ​

本文承接Handler消息发送和Looper主循环。前文已经说明 Handler 最终调用队列入队;这里只追踪这个入队点怎样改变链表,以及为什么“插到队头”不等于“任何情况下都立刻唤醒”。

1. 入队前提 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:enqueueMessage()

java
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
    }
}

入队前有三道边界:屏障 Message 没有 target,不能走普通入队;已经在队列或正在使用的 Message 不能再次发送;队列退出后,发送失败并回收 Message。只有通过这些检查,队列才会标记消息在用并写入绝对执行时间。

2. 队头插入 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:enqueueMessage()

java
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
    if (p == null) {
        mLast = mMessages;
    }
}

空队列、when == 0 的队头发送,或新时间早于当前队头时,消息直接成为新头节点。只有 Looper 当前确实阻塞在 native poll(mBlocked)时才需要唤醒;如果 Looper 正在执行另一条消息,入队不会额外唤醒它。

3. 中间插入 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:enqueueMessage()

java
else {
    needWake = mBlocked && p.target == null && msg.isAsynchronous();

    if (when >= mLast.when) {
        needWake = needWake && mAsyncMessageCount == 0;
        msg.next = null;
        mLast.next = msg;
        mLast = msg;
    } else {
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
            if (needWake && p.isAsynchronous()) {
                needWake = false;
            }
        }
        if (p == null) {
            mLast = msg;
        }
        msg.next = p;
        prev.next = msg;
    }
}

普通延迟消息按 when 升序插入,when 相等时沿已有顺序向后走。尾插入和中间插入通常不用唤醒,因为队头等待时间没有变;例外是队头为同步屏障、Looper 正在阻塞,而新异步消息可能成为屏障后的可执行消息。

4. 异步计数 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:enqueueMessage()、next()

java
if (msg.isAsynchronous()) {
    mAsyncMessageCount++;
}

if (needWake) {
    nativeWake(mPtr);
}

mAsyncMessageCount 是队列维护的异步消息数量,用来判断尾部插入是否可能改变屏障等待。它不是优先级数值,也不会改变链表的 when 排序;next() 真正取出异步消息时会递减该计数。

5. 同步屏障 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:postSyncBarrier()、removeSyncBarrier()

java
private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        // 按 when 插入,target 保持 null
        return token;
    }
}

屏障本身也是链表节点,但没有 target,所以不经过普通 enqueueMessage()。next() 遇到它后会跳过后面的同步消息,寻找异步消息;入队逻辑因此必须在屏障位于队头且新消息异步时唤醒等待线程。屏障移除后,如果队头恢复为普通消息,removeSyncBarrier() 也会按需唤醒。

6. 生效时机 ​

入队的锁内动作顺序是:校验 → 标记 in-use → 写 when → 链表插入 → 更新异步计数 → 必要时 native 唤醒。调用者看到 true 时,这些动作已经完成;但消息仍可能等待到期、被屏障阻塞或在退出清理中回收。nativeWake() 只负责让 Looper 重新检查,真正出队仍由 MessageQueue.next() 决定。

7. 失败边界 ​

输入状态行为Message结果
target == null抛 IllegalArgumentException未进入队列
isInUse() == true抛 IllegalStateException保持原使用状态
mQuitting == true记录警告并返回 falsemsg.recycle()
合法消息返回 true标记 in-use 并进入链表

特别要注意:退出后的失败路径使用 msg.recycle(),而已在队列中的消息会由退出清理调用 recycleUnchecked()。两者都回收对象,但触发条件和调用者不同。

8. 测试输入 ​

源码文件:frameworks/base/tests/testables/tests/src/android/testing/TestableLooperTest.java

相关函数:testDelayedMessageDoesntSend()、testMessageSendsAfterDelay()

java
handler.sendMessageDelayed(messageA, 0);
handler.sendMessageDelayed(messageB, 0);
handler.sendMessageDelayed(messageC, 500);

mTestableLooper.processAllMessages();

inOrder.verify(handler).dispatchMessage(messageA);
inOrder.verify(handler).dispatchMessage(messageB);
verify(handler, never()).dispatchMessage(messageC);

测试用两条立即消息和一条 500ms 延迟消息,先不推进虚拟时间,断言前两条按顺序分发、第三条不分发;另一测试推进 500ms 后再处理,才断言第三条出现。它验证的是 when 排序和到期门槛,不等价于真实设备上的 native 阻塞精度。

9. 动手验证 ​

bash
rg -n "boolean enqueueMessage|needWake|mAsyncMessageCount|nativeWake" \
  frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "sendMessageDelayed|testDelayedMessageDoesntSend|testMessageSendsAfterDelay" \
  frameworks/base/tests/testables/tests/src/android/testing/TestableLooperTest.java

读源码时可以在纸上画出三种队头:普通消息、同步屏障、空队列。再分别放入 when=0、更早时间、较晚时间和异步消息,逐项判断是否改变队头、是否改变可执行消息、是否需要 nativeWake()。

10. 边界说明 ​

本文不展开 Handler 如何生成 when,也不展开 next() 的 IdleHandler、native epoll 和完整同步屏障生命周期。这里的“按时间排序”描述的是 Java 链表插入,不代表消息一定按发送线程的墙上时钟执行;源码使用的是 SystemClock.uptimeMillis() 体系。