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()
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()
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()
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()
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()
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()
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)
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()
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
相关测试:延迟消息排序与同一到期时间的顺序测试
handler.sendMessageDelayed(messageA, 0);
handler.sendMessageDelayed(messageB, 0);
handler.sendMessageDelayed(messageC, 500);阅读测试时要记录三件事:输入的 delay、推进或执行队列的动作、最终断言的消息顺序。它能说明 Java 队列的时间排序和测试 Looper 的消费行为,但不能单凭测试推断 Linux epoll_wait() 的唤醒延迟。
9. 复现路径
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;这些主题各有独立源码入口。
