消息机制源码地图
总结篇不应该再次罗列“Handler 做什么、Looper 做什么、MessageQueue 做什么”。真正有用的 总结是给读者一条可以重复执行的阅读路径:从一个入口开始,找到状态 owner,跟到消费者, 再从失败或延迟现象反向定位到同一条路径上的边界。
本文面向已经读过 消息机制总览、Native 层总览、 同步屏障、FrameHandler 帧调度 和 ANR 消息边界 的读者。文章只使用 Handler、Legacy MessageQueue、Java Looper、JNI NativeMessageQueue、libutils Looper 以及三个框架消费者作地图节点,不把地图文章扩写成每个专题的替代品。
读完后,读者应能完成三件事:
- 从
Handler.post()追到 Java 链表、native poll、回调和Message回收; - 解释“已入队、已唤醒、已出队、已完成、已清理”分别由谁拥有;
- 面对消息不执行、主线程卡顿或帧回调延迟时,按源码边界选择下一处搜索位置。
1. 阅读入口
选一个动作
系列中最稳定的入口是 post(Runnable)。它同时覆盖请求封装、队列插入、时间等待、Looper 分发和对象回收;sendMessage()、postDelayed() 和 sendMessageAtFrontOfQueue() 只是 在同一条路径上改变 Message 字段或 when。
源码文件:frameworks/base/core/java/android/os/Handler.java
相关函数:post()、sendMessageAtTime()、enqueueMessage()、getPostMessage()
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
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);
}这里先记住两个字段:callback 是 Runnable 的消费者入口,target 还没有在 getPostMessage() 中写入。真正的 Handler 绑定发生在统一入队函数:
源码文件:frameworks/base/core/java/android/os/Handler.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);
}读源码时,不要把 Handler 说成“执行线程”。它拥有的是 Looper、MessageQueue、Callback 和入队入口;真正消费 Message 的线程由该 Handler 保存的 Looper 决定。
画出边界
下面的图只画 owner 转移,不把每个专题都塞进一张知识图谱。箭头上的动词是后续搜索时应 输入的函数名。
箭头并不表示每一步都在同一个线程执行:post() 可以由任意生产者调用,next()、 dispatchMessage() 和正常路径的回收发生在 Looper 线程;native poll 仍由该线程进入, 但它等待的是文件描述符事件。
2. 一次投递
节点状态
源码文件:frameworks/base/core/java/android/os/Message.java
public long when;
/*package*/ Handler target;
/*package*/ Runnable callback;
/*package*/ Message next;
/*package*/ volatile int flags;这几个字段分别承担时间、消费者、Runnable 入口、队列链接和生命周期标志。Message 不是 一个带有统一 execute() 的任务接口;what/arg1/arg2/obj/data 仍由具体 Handler 解释。
post() 使用 Message.obtain(),但 Message 直到入队时才被标记为 in-use:
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
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) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
// 按 when 插入 mMessages
}
return true;
}于是一次投递至少有四个可观察状态:
| 状态 | 关键字段或动作 | 谁能改变 |
|---|---|---|
| 新建 | flags 未表示 in-use | 创建者 |
| 已入队 | target、when、next、in-use | MessageQueue 锁内 |
| 已出队 | 从 mMessages 摘下,仍是 in-use | MessageQueue.next() |
| 已回收 | 引用清理,可能进入 sPool | Looper 或队列清理路径 |
“发送成功”只覆盖第二行。它不代表第四行已经发生,也不代表 Runnable 已经开始。
延迟生效
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
final long now = SystemClock.uptimeMillis();
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
msg.markInUse();
return msg;
}
}实际源码还要处理屏障头、mLast、异步消息计数和 IdleHandler;上面的片段只保留时间判断。 when 使用 SystemClock.uptimeMillis(),因此“延迟 100ms”是消息具备出队条件的时间, 不是 Runnable 获得 CPU 的硬保证。
3. 一次等待
Java轮询
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 重新查看 mMessages、同步屏障和 mQuitting
// 找到到期消息则摘下并返回
}
}nativePollOnce() 返回后,Java 代码仍必须重新查看自己的链表。native 唤醒是“等待条件 可能改变”的信号,不是“某一个 Message 已经交付”的通知;多个生产者可能在一次唤醒前后 合并入队,队列也可能在醒来后已经退出。
JNI转发
源码文件:frameworks/base/core/jni/android_os_MessageQueue.cpp
相关函数:android_os_MessageQueue_nativePollOnce()、android_os_MessageQueue_nativeWake()、 NativeMessageQueue::pollOnce()
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
void NativeMessageQueue::wake() {
mLooper->wake();
}JNI 对象保存 poll 期间的 JNIEnv 和 Java poll 对象,供 FD 回调返回 Java;poll 结束后立即 清空它们。这里的 owner 是 NativeMessageQueue,不是 Java Message,也不是 Handler。
epoll唤醒
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::Looper()、Looper::pollInner()、Looper::wake()、Looper::awoken()
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false), mPolling(false),
mEpollRebuildRequired(false),
mRequestsGeneration(0), mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0,
"Could not make wake event fd: %s", strerror(errno));
std::scoped_lock _l(mLock);
rebuildEpollLocked();
}构造完成后,wake fd 已经进入 epoll 集合。队列没有可立即消费的工作时,poll 路径才会使用 Java 传入的 timeout 进入等待;新的队头消息使旧 timeout 失效时,生产者沿 wake 路径写入 eventfd。
int eventCount = epoll_wait(epollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(
write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t) && errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d", mWakeEventFd.get());
}
}
void Looper::awoken() {
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}eventfd 只负责把阻塞中的 epoll_wait() 拉回处理路径;Java 队列会在返回后再次判断 when、屏障和退出状态。不要把 eventfd 写入次数等同于消息数量。
4. 一次消费
出队以后
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:loopOnce()、loop()
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next();
if (msg == null) {
return false;
}
final Observer observer = sObserver;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
}
msg.recycleUnchecked();
return true;
}
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
final int thresholdOverride = getThresholdOverride();
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}实际 loopOnce() 在 try 前后还处理 Printer、Observer、慢消息、trace 和 Binder identity。 这里最重要的顺序是:next() 返回 Message,target Handler 执行,正常路径末尾回收。若分发抛出 异常,会先通知 Observer 并重新抛出;不能把 Observer 当成异常吞噬器,也不能把异常路径直接 等同于正常回收路径。
三路分发
源码文件:frameworks/base/core/java/android/os/Handler.java
相关函数:dispatchMessageImpl()
public void dispatchMessageImpl(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}这是固定优先级选择:Message callback 优先;没有 callback 时,Handler.Callback 可以消费并 以 true 截止;否则落到 handleMessage()。what 的含义属于最后的具体消费者,MessageQueue 不会解释它。
5. 三个落点
应用事务
系统通过 Binder 把 ClientTransaction 送到应用进程后,ClientTransactionHandler 的 scheduleTransaction() 先执行准备,再发送 ActivityThread.H.EXECUTE_TRANSACTION:
源码文件:frameworks/base/core/java/android/app/ClientTransactionHandler.java
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}源码文件:frameworks/base/core/java/android/app/ActivityThread.java
相关类型和函数:ActivityThread.H、handleMessage()
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
final ClientTransactionListenerController controller =
ClientTransactionListenerController.getInstance();
controller.onClientTransactionStarted();
try {
mTransactionExecutor.execute(transaction);
} finally {
controller.onClientTransactionFinished();
}
break;这里的 Handler 不是 Activity 生命周期的 owner;它只把 Message 路由给 TransactionExecutor。事务状态、Activity 状态和回调完成分别由不同对象拥有。排查“Binder 已返回但界面还未变化”时,应从 scheduleTransaction()、EXECUTE_TRANSACTION 和 TransactionExecutor.execute() 继续,而不是停在 Handler。
绘制调度
ViewRootImpl.scheduleTraversals() 是消息机制与 UI 绘制的另一个落点。它先设置 mTraversalScheduled,再插入同步屏障,把 traversal callback 交给 Choreographer:
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
相关函数:scheduleTraversals()、doTraversal()、postTraversalBarrier()、 removeTraversalBarrier()
void scheduleTraversals() {
checkThreadCompat();
if (!mTraversalScheduled) {
mTraversalScheduled = true;
postTraversalBarrier();
mChoreographer.postVsyncCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal(long frameTimeNanos) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
removeTraversalBarrier();
performTraversals(frameTimeNanos);
}
}同步屏障的 owner 是 ViewRootImpl 的 traversal 状态,屏障本身由 MessageQueue 存储; Choreographer 的 callback queue 又是另一层 due-time 链表。排查“post 后读取到旧宽度”时,要 检查 traversal 是否已预约、屏障是否移除,而不能只看 Runnable 是否已经入队。
帧回调
源码文件:frameworks/base/core/java/android/view/Choreographer.java
相关函数:scheduleFrameLocked()、FrameDisplayEventReceiver.onVsync()、run()
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}上面的分支只负责预约帧:同线程可以直接请求 VSYNC,跨线程则先投递异步调度消息。真正收到 显示事件后,FrameDisplayEventReceiver 还会把本次时间戳和 frame 数据保存下来,再创建 另一条异步 Message 进入同一个 Looper。
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData.copyFrom(vsyncEventData);
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}VSYNC 回调不是直接调用 doFrame(),而是先变成异步 Message.callback,再按 VSYNC 时间投递。 这解释了两个现象:它可以穿过同步屏障,但仍会被更早的消息影响;onVsync() 返回也不表示 帧已经执行。
6. 状态收束
正常回收
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:recycle()、recycleUnchecked()、clear()
public void recycle() {
if (isInUse()) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
recycleUnchecked();
}
void recycleUnchecked() {
if (MessageQueue.getUseConcurrent()) {
clearReferenceFields();
markInUse();
} else {
clear();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}正常回收清掉 target、callback、obj、data 等引用;Legacy 分支最多把 50 个对象放进 池中,并不承诺每个 Message 都能复用。Concurrent MessageQueue 分支还有不同的引用清理策略, 不能把 Legacy 链表的池行为推广到所有实现。
退出清理
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:quit()、quitSafely()
public void quit() {
clearLooperDoctor();
mQueue.quit(false);
}
public void quitSafely() {
clearLooperDoctor();
mQueue.quit(true);
}源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:quit(boolean)、removeAllMessagesLocked()、removeAllFutureMessagesLocked()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}quit() 删除全部待处理消息;quitSafely() 只保留已经到期的消息。两者都会让后续入队 失败并唤醒等待中的 Looper。已经从队列摘下、正在执行的 Runnable 不会被这两个函数强制中断。
取消竞态
源码文件:frameworks/base/core/java/android/os/Handler.java
public final void removeCallbacks(@NonNull Runnable r) {
mQueue.removeMessages(this, r, null);
}如果 MessageQueue.next() 已经摘下节点,removeCallbacks() 就找不到它;如果节点仍在链表中, 队列会匹配 target/callback/obj 后回收。这个边界是排查“remove 后任务仍执行”的第一处源码: 先判断任务处于待出队、已出队还是已完成,不要只检查调用是否返回。
7. 问题反查
不执行
遇到“post 返回 true 但没有看到回调”,按以下顺序搜索:
Handler.post():确认是否确实返回true,以及绑定的是哪个 Looper。MessageQueue.enqueueMessage():确认队列是否已经mQuitting,消息when是否在未来。MessageQueue.next():确认是否被同步屏障挡住,是否存在更早消息或 Looper 没有继续循环。Looper.loopOnce():确认是否已经拿到 Message,是否在dispatchMessage()前抛异常。Handler.dispatchMessageImpl():确认 Message callback、Handler.Callback 和 handleMessage 的选择。
这五步对应五个 owner,不应一上来修改 Runnable 本身。
主线程卡顿
“主线程卡顿”至少有两种不同位置:
| 现象 | 优先查看 | 可能的状态 |
|---|---|---|
| 消息迟迟没有开始 | MessageQueue.next()、native poll | 等待、屏障、前序消息 |
| 已开始但迟迟没有完成 | Looper.loopOnce()、目标消费者 | 回调内部阻塞或异常 |
| 输入后布局未更新 | ViewRootImpl、Choreographer | traversal 已预约但尚未消费 |
| 帧事件没有按预期执行 | FrameDisplayEventReceiver | VSYNC pending、消息时间或队列拥塞 |
Looper.Observer、Printer 和 slow log 可以标记 dispatch 边界,但它们不自动给出业务根因; 根因仍要回到目标 Handler 或框架消费者。
Native无响应
如果堆栈停在 nativePollOnce(),先区分“正常睡眠”和“异常永不唤醒”:
- 正常睡眠:队列没有到期消息,
epoll_wait()等待 timeout 或 eventfd。 - 新消息未唤醒:检查
enqueueMessage()的needWake,尤其是同步屏障头和异步消息。 - native poll 返回但没有 Java 消息:检查返回后的 Java 链表、
mQuitting和mPtr。 - FD 回调异常:检查
NativeMessageQueue的mPollEnv/mPollObj生命周期和异常缓存。
8. 实验输入
线程归属
源码文件:frameworks/base/core/tests/coretests/src/android/os/HandlerThreadTest.java
th1.start();
assertTrue(th1.isAlive());
assertNotNull(th1.getLooper());
final Handler h1 = new Handler(th1.getLooper()) {
public void handleMessage(Message msg) {
assertEquals(TEST_WHAT, msg.what);
assertEquals(mLooperTid, Process.myTid());
mGotMessage = true;
synchronized (this) {
notifyAll();
}
}
};
h1.sendMessage(h1.obtainMessage(TEST_WHAT));输入是尚未启动的 HandlerThread;动作是启动、等待 Looper 就绪、绑定 Handler 并发送消息; 断言是 what 正确且消费线程 tid 与 Looper 初始化线程相同。它能说明 Handler 绑定和单线程 消费,不能说明队列公平性、VSYNC 时序或多个 MessageQueue 实现的性能。
排序清理
源码文件:frameworks/base/core/tests/coretests/src/android/os/MessageQueueTest.java
final long future = SystemClock.uptimeMillis() + LONG_DELAY_MS;
handler.sendEmptyMessageAtTime(0, future);
handler.sendEmptyMessageAtTime(1, future + 1);
handler.sendEmptyMessageAtTime(4, future + 4);
handler.sendEmptyMessageAtTime(3, future + 3);
handler.sendEmptyMessageAtTime(2, future + 2);
Message m = queue.peekLastMessageForTest();
assertEquals(m.what, 4);测试输入故意乱序发送五条未来消息,断言队尾是最大 when 的消息;另一个测试发送 100 条 未来消息、调用 resetForTest(),再断言队尾为空。前者说明时间排序,后者说明测试重置的 清理效果;它们不能证明回调的实时延迟或生产环境可以随意调用 reset。
空闲行为
源码文件:frameworks/base/core/tests/coretests/src/android/os/IdleHandlerTest.java
queueIdle() 返回 false 的测试断言回调只执行一次,返回 true 的测试断言它会再次执行。 输入是延迟消息和不同保留返回值,动作是让队列经历“有消息 → 空闲 → 再有消息 → 再空闲”, 断言是回调计数。这个实验适合验证 IdleHandler 生命周期,不适合推导某台设备上的空闲时间。
9. 阅读边界
实现差异
本文主线使用 LegacyMessageQueue,因为它的链表、同步屏障和 JNI 等待路径最适合展示调用 关系。Android 17 源码目录中还存在 CombinedMessageQueue、CombinedDeliMessageQueue 等 实现入口;它们可能改变数据结构和复用细节,但不改变本文用于定位的几个抽象边界:Handler 绑定 Looper、队列决定可消费节点、Looper 调用 target、消费者拥有业务状态。
结论边界
以下结论不能从本文主线直接推出:
post()的耗时、吞吐或内存收益;这些需要具体设备和队列实现的实验。- VSYNC 的固定周期或 SurfaceFlinger 的完整生产链;本文只追踪 Java 侧接收和再投递。
- 所有 system_server Handler 的线程表;每个服务必须回到自己的构造、Looper 和消费者。
removeCallbacks()能停止已运行任务;源码只支持移除仍在队列中的匹配节点。- Observer、Printer 或 slow log 能自动确定业务根因;它们只标记观察边界。
10. 后续路线
按问题选择下一篇,而不是按编号从头重读:
| 你要回答的问题 | 继续阅读 |
|---|---|
| 为什么消息在屏障后仍能执行 | 异步消息与 VSYNC |
| 谁创建了工作 Looper,如何收束线程 | HandlerThread 生命周期 |
| native 等待如何监听 FD | NativeLooper addFd |
| 为什么消息执行慢或到达晚 | Looper 消息监控 |
| 为什么取消后仍有回调 | 消息泄漏排查 |
| 为什么输入超时与消息阻塞相关 | ANR 消息边界 |
一次有效的复习任务是:任选一个真实的 Handler.post() 调用,画出 target、when、 mMessages、mPtr 和最终消费者;然后人为加入一个退出、屏障或延迟条件,说明哪一个函数 会改变结果。若能完整复述“入口 → owner → 生效时机 → 消费者 → 清理”,这张源码地图就已经 发挥作用,而不需要记住一张脱离调用顺序的类名清单。
