我们来深入解析 Android 中引入的 SurfaceSyncGroup 同步机制,它主要解决多 Surface 协同显示时的同步问题,避免出现黑屏、闪屏等显示异常。
一、SurfaceSyncGroup 的核心作用:解决多 Surface 显示同步问题
想象你在拼一幅拼图,需要等所有拼图块都到位后才能展示完整画面。SurfaceSyncGroup 就像一个 “拼图协调者”,确保多个 Surface(如 Activity 的主界面、嵌入的 SurfaceView、跨进程的 SurfaceControlViewHost)全部绘制完成后,再统一通知系统显示,避免以下问题:
- 界面撕裂:主界面绘制完成但子 Surface 还在加载,导致部分区域空白。
- 黑屏 / 闪屏:系统提前移除过渡界面(如启动页),但实际内容尚未准备好。
二、核心机制:树状结构与回调链
SurfaceSyncGroup 通过树状结构组织多个 Surface 的同步逻辑,每个节点代表一个需要同步的对象(如 ViewRootImpl、SurfaceView):
1. 树结构初始化:根节点与子节点
-
根节点(mWmsRequestSyncGroup) :由 ViewRootImpl 创建,作为整个同步链的入口,最终负责通知窗口管理器(WMS)显示界面。
-
子节点(mActiveSurfaceSyncGroup) :每个 Surface 对应一个子节点(如 ViewRootImpl 自身),通过
add方法加入根节点的子树。
java
// ViewRootImpl中创建根节点
mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync", t -> {
// 最终回调:通知WMS绘制完成
reportDrawFinished(t, seqId);
});
// 将当前ViewRootImpl的SurfaceSyncGroup加入根节点
mWmsRequestSyncGroup.add(this);
2. 回调链:子节点完成 → 父节点合并 → 根节点通知
- 子节点绘制完成:调用
markSyncReady()标记自身完成,触发父节点的回调包装器ITransactionReadyCallback。 - 父节点合并事务:父节点收集所有子节点的绘制结果(Transaction),合并后检查是否所有子节点已完成(
mPendingSyncs为空)。 - 根节点触发最终通知:所有子节点完成后,根节点调用初始设置的回调,通知 WMS“所有 Surface 已准备好,可以显示”。
三、关键流程解析:从绘制完成到系统通知
1. 创建同步组(createSyncIfNeeded)
-
目标:初始化根节点和子节点,建立树结构。
-
核心步骤:
- 根节点初始化:携带最终通知 WMS 的回调(
reportDrawFinished)。 - 子节点加入:ViewRootImpl 的
mActiveSurfaceSyncGroup作为子节点,通过add方法绑定到根节点。
java
// 根节点负责最终通知WMS mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync", t -> reportDrawFinished(t, seqId)); // 子节点(当前ViewRootImpl)加入根节点 mWmsRequestSyncGroup.add(this); - 根节点初始化:携带最终通知 WMS 的回调(
2. 绘制完成标记(markSyncReady)
-
触发时机:ViewRootImpl 完成 UI 绘制后,调用
mActiveSurfaceSyncGroup.markSyncReady()。 -
核心逻辑:
- 标记自身为 “准备就绪”(
mSyncReady = true)。 - 通知父节点检查是否所有子节点已完成(
checkIfSyncIsComplete)。
java
public void markSyncReady() { mSyncReady = true; // 标记自身完成 checkIfSyncIsComplete(); // 通知父节点检查状态 } - 标记自身为 “准备就绪”(
3. 回调链传递:从子节点到根节点
-
子节点回调:子节点的
mTransactionReadyConsumer先调用自身逻辑(如应用绘制结果),再触发父节点的回调。 -
父节点合并:父节点收集所有子节点的 Transaction,移除已完成的子节点标记(
mPendingSyncs.remove),直到所有子节点完成。 -
根节点通知:当根节点的
mPendingSyncs为空(所有子节点完成),调用初始回调通知 WMS。java
// 子节点回调逻辑(伪代码) mTransactionReadyConsumer = (transaction) -> { this.applyTransaction(); // 子节点自身逻辑 parentCallback.onTransactionReady(transaction); // 触发父节点回调 };
四、为什么需要 SurfaceSyncGroup?解决传统同步的痛点
在 Android 13 及之前,多 Surface 同步依赖松散的回调机制,容易出现:
-
时序混乱:不同 Surface 的绘制完成信号可能无序到达,导致系统提前或滞后显示。
-
跨进程同步难:SurfaceControlViewHost 等跨进程场景缺乏统一协调。
SurfaceSyncGroup 通过树状结构 + 事务合并解决这些问题:
- 统一协调:所有 Surface 的完成信号通过树结构逐级汇聚,根节点确保 “全或无” 的同步。
- 跨进程支持:通过 Binder 机制(
ISurfaceSyncGroup接口),允许不同进程的 Surface 加入同一同步组。 - 超时保护:内置超时机制(
addTimeout),避免某个 Surface 长时间未完成导致整体阻塞。
五、关键类与成员:从代码视角看实现
1. 核心类
- SurfaceSyncGroup:管理同步逻辑,维护父节点、子节点列表、回调链。
- ISurfaceSyncGroup:跨进程通信接口,允许子节点向父节点发送完成信号。
- ITransactionReadyCallback:回调包装器,用于合并子节点的绘制结果(Transaction)。
2. 关键成员
- mTransactionReadyConsumer:每个节点的最终回调,子节点完成后触发父节点逻辑。
- mPendingSyncs:父节点维护的子节点列表,每完成一个子节点就移除一个标记,为空时触发最终通知。
- mSyncReady:标记当前节点是否已完成绘制,由
markSyncReady设置。
六、总结:SurfaceSyncGroup 如何工作?
- 初始化阶段:ViewRootImpl 创建根节点,将自身的 SurfaceSyncGroup 作为子节点加入,建立树结构。
- 绘制阶段:每个 Surface 完成绘制后,调用
markSyncReady标记自身完成,触发父节点的回调链。 - 同步阶段:父节点收集所有子节点的完成信号,合并绘制结果,直到所有子节点完成。
- 通知阶段:根节点触发初始设置的回调,通知 WMS 所有 Surface 已准备好,系统最终显示界面。
七、典型场景:避免启动页闪屏
假设一个 Activity 包含主界面(ViewRootImpl)和一个视频 SurfaceView:
- 主界面和 SurfaceView 各自的 SurfaceSyncGroup 加入同一个根节点。
- 主界面先完成绘制,但 SurfaceSyncGroup 不会立即通知系统,而是等待 SurfaceView 完成。
- 当 SurfaceView 也完成绘制后,根节点统一通知系统显示,避免主界面提前显示而视频区域空白的问题。
八、核心优势
-
可靠性:确保所有相关 Surface 同步完成,避免部分显示异常。
-
高效性:通过事务合并减少系统调用,提升多 Surface 场景的协同效率。
-
兼容性:支持跨进程场景(如 SurfaceControlViewHost),统一管理不同组件的绘制状态。
通过 SurfaceSyncGroup,Android 在多 Surface 协同显示上实现了更可靠的同步机制,确保用户看到的界面始终是完整且一致的,尤其在复杂界面或跨进程组件交互时,显著提升了显示稳定性和用户体验。