Skip to content

消息机制源码地图

用一次投递、一次等待和三个框架落点串起 Handler 消息机制,建立从症状反查入口、状态、消费者和清理路径的源码阅读地图。

基于android-17.0.0_r1
AndroidHandlerLooperMessageQueue源码阅读

消息机制源码地图 ​

总结篇不应该再次罗列“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()

java
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

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

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()

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) {
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        // 按 when 插入 mMessages
    }
    return true;
}

于是一次投递至少有四个可观察状态:

状态关键字段或动作谁能改变
新建flags 未表示 in-use创建者
已入队target、when、next、in-useMessageQueue 锁内
已出队从 mMessages 摘下,仍是 in-useMessageQueue.next()
已回收引用清理,可能进入 sPoolLooper 或队列清理路径

“发送成功”只覆盖第二行。它不代表第四行已经发生,也不代表 Runnable 已经开始。

延迟生效 ​

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

相关函数:next()

java
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()

java
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()

cpp
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()

cpp
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。

cpp
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()

java
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()

java
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

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()

java
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()

java
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()

java
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。

java
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()

java
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()

java
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()

java
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

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

如果 MessageQueue.next() 已经摘下节点,removeCallbacks() 就找不到它;如果节点仍在链表中, 队列会匹配 target/callback/obj 后回收。这个边界是排查“remove 后任务仍执行”的第一处源码: 先判断任务处于待出队、已出队还是已完成,不要只检查调用是否返回。

7. 问题反查 ​

不执行 ​

遇到“post 返回 true 但没有看到回调”,按以下顺序搜索:

  1. Handler.post():确认是否确实返回 true,以及绑定的是哪个 Looper。
  2. MessageQueue.enqueueMessage():确认队列是否已经 mQuitting,消息 when 是否在未来。
  3. MessageQueue.next():确认是否被同步屏障挡住,是否存在更早消息或 Looper 没有继续循环。
  4. Looper.loopOnce():确认是否已经拿到 Message,是否在 dispatchMessage() 前抛异常。
  5. Handler.dispatchMessageImpl():确认 Message callback、Handler.Callback 和 handleMessage 的选择。

这五步对应五个 owner,不应一上来修改 Runnable 本身。

主线程卡顿 ​

“主线程卡顿”至少有两种不同位置:

现象优先查看可能的状态
消息迟迟没有开始MessageQueue.next()、native poll等待、屏障、前序消息
已开始但迟迟没有完成Looper.loopOnce()、目标消费者回调内部阻塞或异常
输入后布局未更新ViewRootImpl、Choreographertraversal 已预约但尚未消费
帧事件没有按预期执行FrameDisplayEventReceiverVSYNC 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

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

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 等待如何监听 FDNativeLooper addFd
为什么消息执行慢或到达晚Looper 消息监控
为什么取消后仍有回调消息泄漏排查
为什么输入超时与消息阻塞相关ANR 消息边界

一次有效的复习任务是:任选一个真实的 Handler.post() 调用,画出 target、when、 mMessages、mPtr 和最终消费者;然后人为加入一个退出、屏障或延迟条件,说明哪一个函数 会改变结果。若能完整复述“入口 → owner → 生效时机 → 消费者 → 清理”,这张源码地图就已经 发挥作用,而不需要记住一张脱离调用顺序的类名清单。