阻塞同步消息

插入同步屏障之后,如果没有异步消息,会阻塞同步消息

插入同步屏障后,如果没有异步消息,MessageQueue 是如何阻塞同步消息的?

插入同步屏障后,如果 队列中没有任何异步消息,那么在 MessageQueue.next() 中将:

找不到可执行的消息

调用 nativePollOnce() 进入阻塞状态

• 整个主线程(Looper)挂起等待新的异步消息或屏障移除

关键源码位置:MessageQueue.next()

Message next() {
    for (;;) {
        ...
        Message msg = mMessages;

        if (msg != null && msg.target == null) { // 是同步屏障
            // 🔽 查找异步消息
            Message prevMsg = msg;
            do {
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());

            if (msg != null) {
                // ✅ 找到了异步消息,返回它
                ...
                return msg;
            } else {
                // ❌ 没有异步消息,阻塞
                mBlocked = true;
                nativePollOnce(ptr, -1, ...);
                continue;
            }
        }

        ...
    }
}

🔍 逐步分析关键路径

Step 1:遇到同步屏障

if (msg != null && msg.target == null)

这表示当前的 msg 是一个 同步屏障(barrier)

Step 2:查找是否有异步消息

do {
    msg = msg.next;
} while (msg != null && !msg.isAsynchronous());

• 如果能找到 isAsynchronous() == true 的 msg,就返回它;

• 如果找不到,则走下一步。

Step 3:找不到异步消息 → 阻塞主线程

// 没有异步消息,进入 native 层阻塞等待新的消息或信号
mBlocked = true;
nativePollOnce(ptr, -1, ...); // timeout = -1 → 无限阻塞

这是系统层的阻塞函数(通过 epoll/select 等实现),此时:

• 主线程进入挂起状态

• Looper.loop() 暂停运行

后续必须要有新的异步消息 or 移除屏障,才能继续运行

示例:无异步消息的队列情况

MessageQueue: 
    [barrier] → [msg1 (同步)] → [msg2 (同步)]

执行 MessageQueue.next():

1. 检查 barrier → 找异步消息
2. 找不到 → 调用 nativePollOnce() → 阻塞

结果:msg1, msg2 永远无法被执行,直到:

• 有新异步消息插入

• 或调用 removeSyncBarrier() 移除 barrier

危险后果:忘记移除同步屏障

如果开发者插入屏障后 忘记调用 removeSyncBarrier(),就会导致:

• 所有同步消息被挂起(如点击、布局、动画都卡死)

• 主线程无法继续执行

• 甚至引发 ANR(Application Not Responding)

这就是系统为何设计 ViewRootImpl 明确管理 mTraversalBarrier 并在 doTraversal() 中始终移除它的原因。

总结表格

场景 结果
有同步屏障 + 有异步消息 异步消息被取出,屏障后同步消息被阻塞
有同步屏障 + 无异步消息 nativePollOnce() 阻塞主线程
移除屏障 所有同步消息恢复正常排队执行

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器