Skip to content

Handler消息发送

从 post、sendMessage 和延迟发送入口追到 Handler 的统一入队点,解释时间基准、队头插入、取消标识和同步等待的源码边界。

基于android-17.0.0_r1
AndroidHandlerMessageQueue源码阅读

Handler消息发送 ​

本文面向已经读过 消息机制总览 和 Handler构造 的读者。本文不再介绍 Handler 如何绑定 Looper,而是追踪一条更具体的源码问题:post()、sendMessage()、延迟发送和队头发送为什么看起来是不同 API,最后却都由同一个入队点设置 target、workSourceUid 和异步标记?

读完后,你应能从发送 API 追到 enqueueMessage(),解释 uptimeMillis 和 when 的关系,判断 token 能取消什么,说明 sendMessageAtFrontOfQueue() 为什么会破坏普通顺序,以及 runWithScissors() 在超时和退出时为什么不能当作事务提交。

1. 发送入口 ​

Handler 的发送 API 可以按载荷和时间分成三组:Runnable 入口先包装成 Message;Message 入口直接修改或复用 Message;队头入口绕过相对延迟计算,直接以 when = 0 入队。它们最终都把目标线程交给 Handler 绑定的 MessageQueue。

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

相关函数:post()、postDelayed()、sendMessage()、sendEmptyMessageDelayed()、sendMessageAtFrontOfQueue()

java
public final boolean post(@NonNull Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) return false;
    return enqueueMessage(queue, msg, 0);
}

这些入口的“成功”只表示消息被队列接受。Looper 退出后可能返回 false;即使返回 true,消息也可能在到期前被安全退出清理。发送 API 没有提供“已经执行完成”的承诺。

2. Runnable包装 ​

post(r) 不把 Runnable 存在 Handler 字段中,而是从 Message 对象池取得 Message,把 Runnable 放到 Message.callback。带 token 的重载同时把 token 放入 Message.obj,因此取消时可以用同一对象身份匹配。

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

相关函数:getPostMessage()

java
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}

这解释了一个容易误用的取消行为:removeCallbacks(r) 匹配的是同一个 Handler、同一个 Runnable 引用和空 token;removeCallbacks(r, token) 还要求 Message.obj 与 token 相同。token 不是调度优先级,也不会改变 when。

3. 时间换算 ​

相对延迟只在 API 表面存在。sendMessageDelayed() 先把负延迟归零,再用 SystemClock.uptimeMillis() 加上 delay,转成绝对到期时间交给 sendMessageAtTime()。

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

相关函数:sendMessageDelayed()、sendMessageAtTime()

java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

uptimeMillis 是调度时钟,不是墙上时间。调用方传入绝对时间时,必须使用相同时间基准;把 currentTimeMillis() 传入会让消息与系统校时发生错误耦合。负 delay 被统一当作零,不会形成一个“立刻之前”的特殊队列层级。

4. 统一入队 ​

真正改变 Message 状态的是私有 enqueueMessage()。它设置 target,复制当前线程的 workSourceUid,再根据 Handler 的 asynchronous 属性给 Message 打标,最后把所有工作交给 MessageQueue。

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

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

java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    onBeforeEnqueue(queue, msg, uptimeMillis);
    return queue.enqueueMessage(msg, uptimeMillis);
}

target 决定消息出队后由哪个 Handler 分发;workSourceUid 记录发送时的工作来源,不能等到消费线程再读取 ThreadLocal;mAsynchronous 只影响这个 Handler 发出的 Message,不会把整个队列变成异步队列。onBeforeEnqueue() 是测试和运行时重定向点,但生产实现为空,不改变正常发送语义。

5. 队头插入 ​

sendMessageAtFrontOfQueue() 使用 when = 0 直接入队,绕过 sendMessageAtTime()。在 Legacy MessageQueue 中,when == 0 会优先于当前队头,因此它可以插到所有普通到期消息前面。

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

相关函数:enqueueMessage()

java
if (p == null || when == 0 || when < p.when) {
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
} else {
    Message prev;
    for (;;) {
        prev = p;
        p = p.next;
        if (p == null || when < p.when) break;
    }
    msg.next = p;
    prev.next = msg;
}

这不是普通的“高优先级”属性:它改变了链表位置,可能让正常消息饥饿。源码注释明确警告它会造成 ordering problems 和 starvation,因此它适合框架级特殊场景,不适合把业务任务当作紧急任务反复插队。

6. 取消匹配 ​

取消不是发送的反操作,而是 MessageQueue 对链表做一次扫描。Runnable 取消按 callback 身份匹配,token 再限制 obj;Message 取消则按 what 和 obj 匹配。源码注释还明确指出这些操作最坏是 O(n)。

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

相关函数:removeCallbacks()、removeCallbacks(Runnable, Object)、removeMessages()、removeCallbacksAndMessages()

java
public final void removeCallbacks(@NonNull Runnable r) {
    mQueue.removeMessages(this, r, null);
}

public final void removeCallbacks(@NonNull Runnable r, @Nullable Object token) {
    mQueue.removeMessages(this, r, token);
}

public final void removeCallbacksAndMessages(@Nullable Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

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

相关函数:removeMessages(Handler, Runnable, Object)

java
if (n.target == h && n.callback == r
        && (object == null || n.obj == object)) {
    Message nn = n.next;
    if (n.isAsynchronous()) {
        mAsyncMessageCount--;
    }
    n.recycleUnchecked();
    p.next = nn;
    continue;
}

取消只对仍在链表中的 Message 生效。若 Looper 已取出 Message,或 Runnable 已经开始执行,扫描不会撤销正在运行的代码。需要更强取消语义时,应在 Runnable 内检查独立的取消状态,而不是只依赖移除队列节点。

7. 同步等待 ​

runWithScissors() 是发送 API 中的特殊路径:目标线程相同就直接运行;跨线程则包装成 BlockingRunnable,先 post,再让调用线程等待完成或超时。

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

相关函数:runWithScissors()、BlockingRunnable.postAndWait()

java
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    if (r == null) throw new IllegalArgumentException("runnable must not be null");
    if (timeout < 0) throw new IllegalArgumentException("timeout must be non-negative");

    if (Looper.myLooper() == mLooper) {
        r.run();
        return true;
    }
    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}

public boolean postAndWait(Handler handler, long timeout) {
    if (!handler.post(this)) return false;
    synchronized (this) {
        final long expirationTime = SystemClock.uptimeMillis() + timeout;
        while (!mDone) {
            long delay = expirationTime - SystemClock.uptimeMillis();
            if (timeout > 0 && delay <= 0) return false;
            try {
                if (timeout > 0) wait(delay); else wait();
            } catch (InterruptedException ignored) {
            }
        }
    }
    return true;
}

超时返回 false 不会撤回已经 post 的 Runnable;目标线程稍后仍可能执行它。调用方如果持有目标线程需要的锁再等待,就可能形成死锁。Looper 退出时,post() 可能失败;若使用不安全的立即退出,等待者还可能一直等不到 BlockingRunnable.run() 的通知。

8. 发送测试 ​

Android 17 的 TestableLooperTest 直接把多个消息以不同 delay 放入测试 Looper,再按虚拟时间推进检查消费顺序。这类测试覆盖的是入队时间和队列消费顺序,不等同于真实 native epoll 调度。

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

相关测试:延迟消息排序与同一到期时间的顺序测试

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

阅读测试时要记录三件事:输入的 delay、推进或执行队列的动作、最终断言的消息顺序。它能说明 Java 队列的时间排序和测试 Looper 的消费行为,但不能单凭测试推断 Linux epoll_wait() 的唤醒延迟。

9. 复现路径 ​

bash
rg -n "post\(|postDelayed|sendMessageDelayed|sendMessageAtTime|sendMessageAtFrontOfQueue" \
  frameworks/base/core/java/android/os/Handler.java
rg -n "getPostMessage|workSourceUid|setAsynchronous|enqueueMessage" \
  frameworks/base/core/java/android/os/Handler.java
rg -n "removeMessages\(Handler, Runnable|removeCallbacksAndMessages" \
  frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "sendMessageDelayed|postAtFrontOfQueue|removeCallbacks|runWithScissors" \
  frameworks/base/tests/testables/tests

给定一个延迟任务没有执行的现象,先判断发送 API 返回值,再检查 when 是否仍在未来、Message 是否已被取消、Looper 是否退出,以及 Runnable 是否已经被取出。不要直接把问题归因为“Handler 丢消息”。

10. 边界 ​

本文覆盖 Handler 发送入口到队列操作的 Java 主线,以及取消和同步等待的失败边界。没有展开 MessageQueue.next() 的 native 阻塞、同步屏障的完整搜索算法、Message 对象池、Looper 观察者和 HandlerThread;这些主题各有独立源码入口。