Handler 消息如何从投递走到执行
Handler 消息如何从投递走到执行
工作线程调用 mainHandler.post(task) 后,task 为什么在主线程执行?决定执行线程的不是 post() 的调用者,而是这个 Handler 绑定的 Looper。Handler 只负责把任务放进队列,不会 创建线程,也不会替线程执行代码。
主线程先有 Looper,Handler 才能绑定它
应用进程的主线程从 ActivityThread.main() 进入。Android 在这里准备主 Looper,创建 ActivityThread 并 attach 到系统,最后调用 Looper.loop():
// frameworks/base/core/java/android/app/ActivityThread.java
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
Looper.loop();Looper.prepareMainLooper() 把 Looper 保存在线程局部变量中。同一个线程只能准备一个 Looper;普通工作线程如果没有调用 Looper.prepare(),就不能依赖“当前线程 Looper”构造 Handler。
Looper.loop() 持续从队列取消息。主线程并不是执行完 main() 就退出,而是依靠这条循环 接收生命周期、输入、绘制和业务任务。
消息按执行时间入队
sendMessage()、sendMessageDelayed() 和 post() 最终都会形成一个 Message,并通过 MessageQueue.enqueueMessage() 插入链表。排序依据是目标时间 when,因此它不是只看插入 先后的简单 FIFO 队列。
当新消息成为队头且目标线程正在 poll,队列会唤醒线程;没有到期消息时, MessageQueue.next() 通过 Native poll 等待。发送线程只完成入队动作,消息的业务代码不会 在发送线程上同步执行。
同步屏障会暂时阻止同步消息通过,让标记为异步的消息越过屏障。它与 Choreographer 的帧调度 有关,但不能被理解为“所有 Handler 消息都异步”;普通 Message 默认仍是同步消息。
post() 和 sendMessage() 在哪里分流
Looper 取出消息后调用消息目标 Handler 的 dispatchMessage():
// frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else if (mCallback == null || !mCallback.handleMessage(msg)) {
handleMessage(msg);
}
}post(Runnable) 把 Runnable 放在 msg.callback,因此先执行 Runnable;没有 Runnable 时, 再尝试构造 Handler 时传入的 Handler.Callback;Callback 未消费消息,才调用子类覆写的 handleMessage()。三条分支都运行在 Looper 所在线程。
这个边界对 Framework 源码很重要:Binder 线程收到 ApplicationThread 请求后,常用 Handler 把状态修改转交给应用主线程。看到 sendMessage() 时,应继续追踪 Handler 绑定的 Looper, 不能根据当前调用栈猜执行线程。
后台消息循环使用 HandlerThread
需要长期消息循环的后台线程时,HandlerThread.run() 会在线程内部准备 Looper,然后进入 Looper.loop()。调用方应在线程启动后通过 getLooper() 创建 Handler。
停止时,quit() 会结束循环,不再接收后续消息;quitSafely() 会让已经到期的消息先执行, 再退出。两者都可能让后续投递失败,持有该 Handler 的代码必须处理线程生命周期,不能假定 后台 Looper 永远存在。
卡顿时看 delivery 还是 dispatch
消息慢有两个不同时间段:消息在队列中等待过久是 delivery 慢;Handler 已经开始执行但长时间 不返回是 dispatch 慢。前者通常说明队列前面有阻塞任务,后者直接指向当前消息处理逻辑。
调试设备上可以先观察 Looper 和 Choreographer 相关日志:
adb logcat -v threadtime | grep -E "Slow (delivery|dispatch)|Skipped"部分版本可通过 log.looper.<uid>.<thread>.slow 属性调整慢消息阈值,但这属于调试实现细节, 属性名和生效条件应以目标版本 Looper.java 为准。ANR trace 中的主线程栈则是另一个关键证据: 它只代表采样时刻,需要结合前后日志判断是在执行消息、等待锁,还是处于 Native poll。
源码入口
源码主入口位于 frameworks/base/core/java/android/os/ 下的 Looper.java、Handler.java、 MessageQueue.java、Message.java 和 HandlerThread.java。阅读时沿 sendMessageAtTime() → enqueueMessage() → next() → dispatchMessage() 跟一遍, 就能把发送线程与执行线程分开。