同步屏障与异步消息
面试重要度:⭐⭐⭐⭐⭐
考察频率:字节 85% | 阿里 70% | 腾讯 75%
一、核心概念(10-15%篇幅)
1.1 定义与作用
一句话定义: 同步屏障(Sync Barrier)是一种特殊的 Message,它没有 target(Handler 为 null),用于拦截同步消息,使异步消息优先执行。
为什么重要:
- Android UI 渲染依赖同步屏障保证 Vsync 信号的及时响应
- 是理解 Choreographer 和 View 绘制流程的关键前置知识
- 字节面试高频考点,区分中高级工程师的重要知识点
- 理解 Handler 消息优先级机制的核心
核心特征:
- 同步屏障本身是一个
target == null的特殊 Message - 只能通过
postSyncBarrier()方法添加(hide API) - 添加后,同步消息被阻塞,异步消息可以继续执行
- 必须通过
removeSyncBarrier()移除,否则同步消息永远无法执行
1.2 消息类型对比
| 消息类型 | 特征 | 创建方式 | 使用场景 |
|---|---|---|---|
| 同步消息 | isAsynchronous() = false | 默认创建的消息 | 普通业务逻辑 |
| 异步消息 | isAsynchronous() = true | setAsynchronous(true) | 需要优先处理的任务 |
| 同步屏障 | target == null | postSyncBarrier() | 系统级优先级控制 |
1.3 与其他概念的关系
同步屏障与 MessageQueue 的消息出队机制紧密相关(详见:../03-MessageQueue队列管理/02-消息出队next().md),在 next() 方法中通过判断 target == null 来识别同步屏障,从而实现消息优先级控制。
二、核心原理(50-60%篇幅)
2.1 工作机制
整体流程:
postSyncBarrier() → 屏障插入队列 → next()检测到屏障 → 跳过同步消息 → 只取异步消息 → removeSyncBarrier()
关键步骤详解:
- 添加同步屏障:调用
postSyncBarrier()创建target=null的特殊 Message 并插入队列 - 消息循环检测:
next()方法检测到队首是屏障消息(target == null) - 过滤同步消息:遍历链表跳过所有同步消息,只寻找异步消息
- 异步消息执行:取出异步消息返回给 Looper 分发执行
- 移除屏障:业务完成后调用
removeSyncBarrier()恢复正常消息处理
状态图:
正常状态: 屏障状态:
┌─────────────────────┐ ┌─────────────────────┐
│ 同步1 → 同步2 → 异步1 │ │ 屏障 → 同步1 → 异步1 │
│ ↑ │ │ × ↑ │
│ 取出 │ │ 阻塞 取出 │
└─────────────────────┘ └─────────────────────┘
2.2 源码分析
2.2.1 添加同步屏障 - postSyncBarrier()
// Android 11 源码:frameworks/base/core/java/android/os/MessageQueue.java
/**
* @hide // 注意:这是隐藏API,应用层无法直接调用
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
// 步骤1:生成唯一的屏障令牌(用于后续移除)
final int token = mNextBarrierToken++;
// 步骤2:从对象池获取 Message(复用机制)
final Message msg = Message.obtain();
msg.markInUse();
// 步骤3:关键!设置 when 但不设置 target
// target 为 null 是同步屏障的唯一标识
msg.when = when;
msg.arg1 = token; // 保存令牌,用于移除时匹配
// 注意:这里没有 msg.target = xxx
// 步骤4:按时间顺序插入队列(与普通消息相同逻辑)
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 步骤5:链表插入
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg; // 插入队首
}
return token; // 返回令牌,调用方需保存用于移除
}
}
源码解读:
- 设计意图:通过
target == null这个特殊标记,在不改变 Message 数据结构的情况下实现屏障功能 - token 作用:每个屏障有唯一标识,支持精确移除指定屏障
- hide 注解:系统级 API,防止应用滥用导致消息阻塞
2.2.2 消息出队时处理同步屏障 - next()
// Android 11 源码:frameworks/base/core/java/android/os/MessageQueue.java
Message next() {
// ... 省略 epoll 等待逻辑
for (;;) {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 关键!检测同步屏障
if (msg != null && msg.target == null) {
// 步骤1:发现同步屏障,需要找异步消息
// 遍历链表,跳过所有同步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
// 循环结束:msg 为 null 或 msg 是异步消息
}
if (msg != null) {
if (now < msg.when) {
// 消息未到执行时间,计算等待时长
nextPollTimeoutMillis = (int) Math.min(
msg.when - now, Integer.MAX_VALUE);
} else {
// 步骤2:取出消息(可能是异步消息)
mBlocked = false;
if (prevMsg != null) {
// 从链表中间取出(异步消息场景)
prevMsg.next = msg.next;
} else {
// 从队首取出(正常场景)
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息(或只有同步消息被屏障阻塞)
nextPollTimeoutMillis = -1; // 无限等待
}
// ... IdleHandler 处理逻辑
}
}
}
源码解读:
- 屏障检测条件:
msg.target == null是唯一判断依据 - 遍历策略:使用
do-while跳过同步消息,直到找到异步消息或链表末尾 - prevMsg 作用:记录前驱节点,用于从链表中间取出异步消息
- 阻塞行为:如果屏障后没有异步消息,
nextPollTimeoutMillis = -1导致无限等待
2.2.3 移除同步屏障 - removeSyncBarrier()
// Android 11 源码:frameworks/base/core/java/android/os/MessageQueue.java
/**
* @hide
*/
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 步骤1:遍历链表查找指定 token 的屏障
// 条件:target == null(是屏障) && arg1 == token(匹配令牌)
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
// 未找到指定屏障
throw new IllegalStateException(
"The specified message queue synchronization "
+ "barrier token has not been posted or has already been removed.");
}
// 步骤2:从链表中移除屏障
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false; // 屏障不在队首,无需唤醒
} else {
mMessages = p.next;
// 步骤3:判断是否需要唤醒
// 移除队首屏障后,如果有同步消息待处理,需要唤醒
needWake = mMessages == null || mMessages.target != null;
}
// 步骤4:回收 Message 到对象池
p.recycleUnchecked();
// 步骤5:唤醒阻塞的 next() 方法
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
源码解读:
- 匹配逻辑:双重条件
target == null && arg1 == token确保精确移除 - 异常处理:重复移除或无效 token 会抛出
IllegalStateException - 唤醒条件:只有移除队首屏障且后续有同步消息时才需要唤醒
2.3 异步消息的设置方式
// Android 11 源码:frameworks/base/core/java/android/os/Message.java
/**
* 设置消息为异步消息
* @hide // 同样是隐藏API
*/
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
// FLAG_ASYNCHRONOUS = 1 << 1 = 2
异步 Handler 构造方式:
// Android 11 源码:frameworks/base/core/java/android/os/Handler.java
/**
* @hide
*/
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async; // 标记此 Handler 发送的消息都是异步消息
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 关键:如果 Handler 是异步的,自动设置消息为异步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
2.4 系统级应用 - Choreographer
Choreographer 是同步屏障的典型使用者:
// Android 11 源码:frameworks/base/core/java/android/view/Choreographer.java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// 步骤1:添加同步屏障,保证 Vsync 回调优先执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 步骤2:发送异步消息,等待 Vsync 信号
scheduleVsyncLocked();
}
}
}
void doFrame(long frameTimeNanos, int frame) {
synchronized (mLock) {
// ... 处理帧逻辑
}
try {
// 执行各类回调:INPUT → ANIMATION → TRAVERSAL
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
// 步骤3:帧处理完成,移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
}
}
流程图:
requestLayout()
↓
scheduleTraversals()
↓
postSyncBarrier() ← 添加屏障,阻塞同步消息
↓
scheduleVsyncLocked() ← 注册 Vsync 回调(异步消息)
↓
等待 Vsync 信号...
↓
doFrame() ← Vsync 到来,优先执行
↓
measure → layout → draw
↓
removeSyncBarrier() ← 移除屏障,恢复同步消息
2.5 重要细节与边界条件
细节1:屏障不会自动移除
- 同步屏障必须显式调用
removeSyncBarrier()移除 - 忘记移除会导致同步消息永远无法执行,造成应用无响应
细节2:多个屏障可以共存
- 每个屏障有唯一 token,可以按顺序或指定 token 移除
- 嵌套添加屏障时需要按相反顺序移除
细节3:屏障对 IdleHandler 的影响
- 当存在同步屏障且没有异步消息时,不会执行 IdleHandler
- 因为队列中仍有同步消息待处理,不算"空闲"
细节4:应用层无法直接使用
postSyncBarrier()是@hideAPI- 可通过反射调用,但不推荐(破坏封装,版本兼容性差)
三、实际应用(15-20%篇幅)
3.1 典型场景
场景1:View 绘制优先级保证
- 需求:屏幕刷新(16.6ms)必须及时响应,不能被普通消息延迟
- 使用方式:Choreographer 在 Vsync 到来前添加屏障
- 注意事项:屏障存在期间的同步消息会被延迟
场景2:自定义高优先级任务(需反射)
-
需求:某些关键任务需要优先于其他消息执行
-
使用方式:反射调用
postSyncBarrier()+ 异步 Handler -
注意事项:
- 必须保证移除屏障,否则同步消息永久阻塞
- 反射方式在高版本 Android 可能受限
3.2 反射使用示例(仅供理解原理)
// 警告:仅用于学习,生产环境不推荐
public class SyncBarrierHelper {
public static int postSyncBarrier(MessageQueue queue) {
try {
Method method = MessageQueue.class.getDeclaredMethod(
"postSyncBarrier");
method.setAccessible(true);
return (int) method.invoke(queue);
} catch (Exception e) {
return -1;
}
}
public static void removeSyncBarrier(MessageQueue queue, int token) {
try {
Method method = MessageQueue.class.getDeclaredMethod(
"removeSyncBarrier", int.class);
method.setAccessible(true);
method.invoke(queue, token);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3 最佳实践
推荐做法:
- 理解原理即可,不要在应用层使用同步屏障
- 如需优先级控制,考虑使用独立的 HandlerThread
- 对于 UI 相关任务,信任系统的 Choreographer 机制
常见错误:
- 添加屏障后忘记移除 → 导致 ANR
- 在子线程添加主线程的屏障 → 需要正确获取主线程 MessageQueue
- 滥用异步消息 → 破坏消息顺序,可能导致逻辑错误
四、面试真题解析(20-25%篇幅)
4.1 基础必答题(P5必须掌握)
【高频题1】什么是同步屏障?它的作用是什么?
标准答案(30秒) : 同步屏障是一种特殊的 Message,它的 target 为 null。当 MessageQueue 检测到同步屏障时,会跳过所有同步消息,只处理异步消息。它的作用是实现消息优先级控制,保证高优先级任务(如 UI 渲染)能够及时执行。
深入展开(追问后) : 在 MessageQueue.next() 方法中,会判断队首消息的 target 是否为 null。如果是,说明遇到了同步屏障,此时会遍历链表查找异步消息。这种机制被 Choreographer 用于保证 Vsync 信号的及时响应,确保 UI 绑定能在 16.6ms 内完成。
面试官追问:
-
追问1:同步屏障如何添加和移除?
- 答:通过
MessageQueue.postSyncBarrier()添加,返回一个 token;通过removeSyncBarrier(token)移除。这两个都是 hide API,应用层无法直接调用。
- 答:通过
-
追问2:如果忘记移除同步屏障会怎样?
- 答:同步消息会被永久阻塞,导致 ANR。因为 next() 方法会一直跳过同步消息寻找异步消息,如果没有异步消息就会无限等待。
【高频题2】同步消息和异步消息有什么区别?
标准答案(30秒) : 默认创建的 Message 都是同步消息。异步消息需要通过 Message.setAsynchronous(true) 设置,或者使用异步 Handler 发送。区别在于:当存在同步屏障时,同步消息会被阻塞,而异步消息可以继续执行。
深入展开(追问后) : Message 内部通过 flags 字段的 FLAG_ASYNCHRONOUS 位来标记是否是异步消息。在 next() 方法中通过 msg.isAsynchronous() 判断。普通开发中很少直接使用异步消息,它主要被系统用于 UI 渲染等高优先级场景。
面试官追问:
-
追问1:如何创建异步 Handler?
- 答:Handler 构造函数有一个 async 参数,设为 true 即可。但这是 hide API。在 Android 9 之后可以使用
Handler.createAsync(Looper)方法。
- 答:Handler 构造函数有一个 async 参数,设为 true 即可。但这是 hide API。在 Android 9 之后可以使用
-
追问2:异步消息和同步屏障必须配合使用吗?
- 答:是的。单独的异步消息没有优先级意义,只有在同步屏障存在时,异步消息才能"插队"执行。
【高频题3】MessageQueue.next() 如何处理同步屏障?
标准答案(30秒) : next() 方法会检查队首消息的 target 是否为 null。如果是 null,说明是同步屏障,会进入一个 do-while 循环遍历链表,跳过所有同步消息,直到找到异步消息或到达链表末尾。
深入展开(追问后) : 具体代码逻辑:首先 if (msg != null && msg.target == null) 检测屏障;然后 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()) 遍历寻找异步消息。prevMsg 记录前驱节点,用于从链表中间取出异步消息。
面试官追问:
-
追问1:为什么用 target == null 来标识同步屏障?
- 答:这是一种巧妙的设计。正常消息必须有 target 用于分发,而屏障本身不需要分发,所以用 null 作为特殊标记,不需要额外字段。
-
追问2:如果屏障后面没有异步消息会怎样?
- 答:msg 会遍历到 null,然后 nextPollTimeoutMillis 设为 -1,导致 nativePollOnce() 无限阻塞,直到有新的异步消息入队或屏障被移除。
4.2 进阶加分题(P6/P6+)
【进阶题1】Choreographer 是如何使用同步屏障的?为什么要这样设计?
参考答案: Choreographer 在 scheduleTraversals() 中调用 postSyncBarrier() 添加屏障,然后通过异步消息注册 Vsync 回调。当 Vsync 信号到来时,doFrame() 方法执行完 UI 遍历后调用 removeSyncBarrier() 移除屏障。
这样设计的原因:UI 渲染必须在 16.6ms(60fps)内完成,如果主线程有大量同步消息,可能延迟 Vsync 回调的执行,导致掉帧。通过同步屏障,保证 Vsync 相关的异步消息能够优先执行,不受其他消息影响。
追问:如果 doFrame() 执行时间超过 16.6ms 会怎样?
- 答:会导致掉帧。但同步屏障至少保证了 Vsync 回调能及时开始执行。掉帧是 doFrame() 内部逻辑(measure/layout/draw)耗时过长导致的,这需要通过优化布局层级、减少过度绘制等方式解决。
【进阶题2】同步屏障机制存在什么风险?如何避免?
参考答案: 主要风险:
- 消息饿死:如果屏障长时间不移除,同步消息会被无限期阻塞
- 内存泄漏:被阻塞的 Message 持有外部引用无法释放
- ANR:关键的同步消息无法执行,导致应用无响应
系统的避免策略:
- Choreographer 在 finally 块中移除屏障,确保异常情况也能移除
- 屏障添加和移除通常在同一方法调用链中,减少遗漏风险
- 系统 API 是 hide 的,防止应用层误用
追问:应用层如何实现类似的优先级控制?
- 答:1)使用独立的 HandlerThread 处理高优先级任务;2)使用 Handler.postAtFrontOfQueue() 插入队首(但仍是同步消息);3)考虑使用协程的调度器实现优先级控制。
【进阶题3】为什么 postSyncBarrier() 是 hide API?
参考答案: 设计考量:
- 安全性:滥用同步屏障会导致消息阻塞和 ANR,普通开发者难以正确使用
- 封装性:这是系统级的优化机制,与 Choreographer、ViewRootImpl 等系统组件紧密耦合
- 兼容性:hide API 可以在后续版本中修改实现细节,不影响公开 API 契约
- 替代方案:应用层有其他方式实现优先级控制,不需要直接操作同步屏障
追问:Android 9 之后的 Handler.createAsync() 为什么可以公开?
- 答:createAsync() 只是创建异步 Handler,发送的消息带有异步标记。但没有同步屏障配合,异步消息和同步消息没有优先级区别,不会造成阻塞风险,所以可以公开。
4.3 实战场景题
【场景题】线上发现偶发 ANR,堆栈显示卡在 MessageQueue.next(),如何排查是否与同步屏障有关?
问题分析: ANR 卡在 next() 可能原因:
- 同步屏障未移除,且没有异步消息
- 消息处理耗时过长
- Native 层 epoll_wait 异常
排查思路:
- 检查堆栈:确认是阻塞在 nativePollOnce() 还是 synchronized 锁等待
- 分析 trace:查看是否有大量待处理的同步消息
- 代码审查:搜索项目中是否有反射调用 postSyncBarrier()
- 系统日志:检查 Choreographer 相关日志,确认 doFrame() 是否正常完成
解决方案:
- 如果是自定义代码添加的屏障,确保在 try-finally 中移除
- 如果是系统屏障,检查 View 绘制流程是否有异常
- 添加监控:Hook MessageQueue,检测屏障存在时间
五、对比与总结
5.1 关键 API 对比
| API/方法 | 作用 | 可见性 | 使用场景 |
|---|---|---|---|
postSyncBarrier() | 添加同步屏障 | @hide | 系统级优先级控制 |
removeSyncBarrier(token) | 移除同步屏障 | @hide | 恢复正常消息处理 |
Message.setAsynchronous(true) | 设置异步消息 | @hide | 配合屏障使用 |
Handler.createAsync(Looper) | 创建异步Handler | public (API 28+) | 发送异步消息 |
postAtFrontOfQueue() | 插入队首 | public | 提高优先级(仍是同步消息) |
5.2 核心要点速记
一句话记忆: 同步屏障是 target 为 null 的特殊 Message,用于阻塞同步消息让异步消息优先执行,是 Android UI 渲染及时响应的关键保障。
3个关键点:
- 同步屏障通过
target == null标识,在 next() 中被识别 - 屏障存在时,会遍历跳过同步消息,只取异步消息
- Choreographer 使用屏障保证 Vsync 回调优先执行
面试官最爱问:
- 同步屏障的实现原理和 next() 的处理逻辑
- Choreographer 如何使用同步屏障保证 UI 渲染
- 同步屏障的风险和应用层的替代方案
六、关联知识点
前置知识:
- MessageQueue 消息出队机制(详见:
../03-MessageQueue队列管理/02-消息出队next().md) - Handler 基本工作流程(详见:
../01-Handler基础/03-Handler工作流程.md)
后续扩展:
- Choreographer 工作原理
- ViewRootImpl 与 View 绘制流程
- Vsync 信号机制
相关文件:
../03-MessageQueue队列管理/02-消息出队next().md- next() 方法完整逻辑../03-MessageQueue队列管理/04-epoll机制.md- Native 层阻塞唤醒../06-IdleHandler/IdleHandler原理与应用.md- 空闲消息处理机制