阻塞同步消息
插入同步屏障之后,如果没有异步消息,会阻塞同步消息
插入同步屏障后,如果没有异步消息,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() 阻塞主线程 |
移除屏障 | 所有同步消息恢复正常排队执行 |