解析 Android 中引入的 SurfaceSyncGroup 同步机制

209 阅读5分钟

我们来深入解析 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)

  • 目标:初始化根节点和子节点,建立树结构。

  • 核心步骤

    1. 根节点初始化:携带最终通知 WMS 的回调(reportDrawFinished)。
    2. 子节点加入:ViewRootImpl 的mActiveSurfaceSyncGroup作为子节点,通过add方法绑定到根节点。

    java

    // 根节点负责最终通知WMS
    mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync", t -> reportDrawFinished(t, seqId));
    // 子节点(当前ViewRootImpl)加入根节点
    mWmsRequestSyncGroup.add(this); 
    

2. 绘制完成标记(markSyncReady)

  • 触发时机:ViewRootImpl 完成 UI 绘制后,调用mActiveSurfaceSyncGroup.markSyncReady()

  • 核心逻辑

    1. 标记自身为 “准备就绪”(mSyncReady = true)。
    2. 通知父节点检查是否所有子节点已完成(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 通过树状结构 + 事务合并解决这些问题:

  1. 统一协调:所有 Surface 的完成信号通过树结构逐级汇聚,根节点确保 “全或无” 的同步。
  2. 跨进程支持:通过 Binder 机制(ISurfaceSyncGroup接口),允许不同进程的 Surface 加入同一同步组。
  3. 超时保护:内置超时机制(addTimeout),避免某个 Surface 长时间未完成导致整体阻塞。

五、关键类与成员:从代码视角看实现

1. 核心类

  • SurfaceSyncGroup:管理同步逻辑,维护父节点、子节点列表、回调链。
  • ISurfaceSyncGroup:跨进程通信接口,允许子节点向父节点发送完成信号。
  • ITransactionReadyCallback:回调包装器,用于合并子节点的绘制结果(Transaction)。

2. 关键成员

  • mTransactionReadyConsumer:每个节点的最终回调,子节点完成后触发父节点逻辑。
  • mPendingSyncs:父节点维护的子节点列表,每完成一个子节点就移除一个标记,为空时触发最终通知。
  • mSyncReady:标记当前节点是否已完成绘制,由markSyncReady设置。

六、总结:SurfaceSyncGroup 如何工作?

  1. 初始化阶段:ViewRootImpl 创建根节点,将自身的 SurfaceSyncGroup 作为子节点加入,建立树结构。
  2. 绘制阶段:每个 Surface 完成绘制后,调用markSyncReady标记自身完成,触发父节点的回调链。
  3. 同步阶段:父节点收集所有子节点的完成信号,合并绘制结果,直到所有子节点完成。
  4. 通知阶段:根节点触发初始设置的回调,通知 WMS 所有 Surface 已准备好,系统最终显示界面。

七、典型场景:避免启动页闪屏

假设一个 Activity 包含主界面(ViewRootImpl)和一个视频 SurfaceView:

  1. 主界面和 SurfaceView 各自的 SurfaceSyncGroup 加入同一个根节点。
  2. 主界面先完成绘制,但 SurfaceSyncGroup 不会立即通知系统,而是等待 SurfaceView 完成。
  3. 当 SurfaceView 也完成绘制后,根节点统一通知系统显示,避免主界面提前显示而视频区域空白的问题。

八、核心优势

  • 可靠性:确保所有相关 Surface 同步完成,避免部分显示异常。

  • 高效性:通过事务合并减少系统调用,提升多 Surface 场景的协同效率。

  • 兼容性:支持跨进程场景(如 SurfaceControlViewHost),统一管理不同组件的绘制状态。

通过 SurfaceSyncGroup,Android 在多 Surface 协同显示上实现了更可靠的同步机制,确保用户看到的界面始终是完整且一致的,尤其在复杂界面或跨进程组件交互时,显著提升了显示稳定性和用户体验。