从零开始给 Surface 实现 SurfaceSyncGroup

背景

我的上一篇文章讲到了给 Surface 实现帧同步,作为实现帧同步的重要手段,在做动画需求,特别是跨进程同步渲染的需求中发挥了重要的作用。但还有另一种跨进程渲染的方案,在 Android13 被提了出来,这便是 SurfaceSyncGroupSurfaceSyncGroup 是用来替代 SurfaceSyncer 的方案的,SurfaceSyncer 我没用过(Android13 发布的时候我刚工作一年),所以就不提了,不过值得注意的是,这个用来帧同步的 SurfaceSyncGroup 居然是作为开放 API 发布的,难得见到谷歌慷慨了一回啊,之前图形绘制相关的 API,不管是 BLAST 相关还是 SurfaceSyncer 都是标明了 hide 接口的。

所以我为什么老是需要用到帧同步这些东西呢,因为我在去年年末的时候接手了桌面的窗口动画业务,特别是在后续的需求中引入多个 Surface 动画之后,有些场景并不能单纯依靠事务。比如绘制的同步就不能依靠我上一篇文章中的那种方法,这就涉及到 Surface 绘制的原理了,绘制是一种隐式的事务,在绘制操作从主线程转移到 RenderThread 上帧渲染完成之后,缓冲区就会挂到一个 Transaction 上,然后这个 Transaction 被发送到 SurfaceFlinger 去进行合成。这点可以通过 Winscope 来验证,在 WindscopeTranscation 一栏,是可以找到带有 BufferTransaction 的。

关于 SurfaceSyncGroup 的原理,网上一搜讲解很多,我这里就不详细展开了,简单来说就是通过一个根 group,来管理所有的子 group。对于这个根 group,如果是本地进程就由本地进程管理,如果是跨进程则通过 WMS 来管理。每个子 group 在绘制完成之后需要调用 markSyncReady 来通知根 group,然后根 group 会检查是否所有的子 group 已经全部准备好,如果全部准备好了,就调用 Transaction#apply 来应用事务。那怎么做到同步帧缓冲的呢?想起我上面说的了吗,绘制也是一种隐式的事务,最终也是要转为事务来应用的,只不过对于开发者来说是个黑箱,无感知而已。

那么问题来了,既然要做绘制同步,我们肯定是要拦截掉带有绘制 BufferTransaction 的,该怎么做到的呢?诶,这时候就要请出我们万能的 BLASTBufferQueue 了,不得不说这真是个好玩意啊。

虽然 BLASTBufferQueue 也是个 hide api,不过系统开发还在意这些干什么呢?

接下来,我们接着上一篇文章的代码,给一个独立创建的 Surface 实现 SurfaceSyncGroup 的功能。

参考

首先,我们肯定是要示例来学会去使用 SurfaceSyncGroup 的,那么哪里有最好的示例呢?答案就在 AOSP 源码里,SurfaceView 就是一个现成的例子,我们来看下 SurfaceSyncGroup#add(SurfaceView, Consumer) 方法里干了些什么吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@UiThread
public boolean add(SurfaceView surfaceView,
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
// 创建一个 SurfaceSyncGroup
SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName());
// 这里调用 SurfaceSyncGroup#add(ISurfaceSyncGroup, boolean, Runnable)
// 这个是所有的重载 add 方法最终调用的地方,在这里管理被添加到当前 group 的子 group
if (add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
null /* runnable */)) {
// 添加成功之后,回调一个闭包给调用者,调用者随后调用这个闭包,并开始 Surface 的绘制
frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> {
// 通过 syncNextFrame 来获取带有下一帧 buffer 的事务
surfaceSyncGroup.addTransaction(transaction);
surfaceSyncGroup.markSyncReady();
}));
return true;
}
return false;
}

上面的重点就是 SurfaceViewsyncNextFrame 方法,这个方法最终调用到了 BLASTBufferQueue#syncNextTransaction(Consumer) 方法, 最终终于到我们的主角出场了,看下这个方法的注释是怎样的吧:

1
2
3
4
5
6
7
8
9
10
/**
* Send a callback that accepts a transaction to BBQ. BBQ will acquire buffers into the a
* transaction it created and will eventually send the transaction into the callback
* when it is ready.
* @param callback The callback invoked when the buffer has been added to the transaction. The
* callback will contain the transaction with the buffer.
*/
public boolean syncNextTransaction(@NonNull Consumer<SurfaceControl.Transaction> callback) {
return syncNextTransaction(true /* acquireSingleBuffer */, callback);
}

看到了吧,说是会回调带有 BufferTransactioncallback。那这下就好办了,我们照猫画虎就可以了。

实现

首先需要声明当前活跃的 SurfaceSyncGroup

1
2
3
4
5
6
7
8
9
10
...
private SurfaceSyncGroup mActiveSyncGroup;
...
private SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
if (mActiveSyncGroup == null) {
mActiveSyncGroup = new SurfaceSyncGroup("sync");
}
return mActiveSyncGroup;
}
...

然后注册帧回调,用于将帧 Buffer 事务合并进 SurfaceSyncGroup 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private void setupFrameCallbacksForSync() {
final SurfaceSyncGroup activeSyncGroup = mActiveSyncGroup;
if (activeSyncGroup == null) {
// 没有请求拦截这一帧的事务,跳过注册回调
return;
}
mActiveSyncGroup = null;
registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
}

@Override
public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult,
long frame) {
// 绘制失败,直接收集所有事务并且 markSyncReady,避免影响其他 group
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
activeSyncGroup.addTransaction(
mBLASTBufferQueue.gatherPendingTransactions(frame));
activeSyncGroup.markSyncReady();
return null;
}
// 请求下一帧事务
boolean result = mBLASTBufferQueue.syncNextTransaction(transaction -> {
activeSyncGroup.addTransaction(transaction);
activeSyncGroup.markSyncReady();
});
// 如果失败了,直接 markSyncReady,避免影响其他 group
if (!result) {
activeSyncGroup.markSyncReady();
}
return didProduceBuffer -> {
if (!didProduceBuffer) {
// 没有成功绘制,则清除之前设置的 sync 事务
mBLASTBufferQueue.clearSyncTransaction();
// 并且收集所有事务,markSyncReady 避免影响其他 group
activeSyncGroup.addTransaction(
mBLASTBufferQueue.gatherPendingTransactions(frame));
activeSyncGroup.markSyncReady();
}
};
}
});
}

最后,在 HwuiContextunlockAndPost 方法中,注册该回调,注意是要在绘制开始前注册的。

1
2
3
4
5
6
7
8
void unlockAndPost(Canvas canvas) {
...
// 设置绘制 sync 事务回调
setupFrameCallbacksForSync();
// 在这里即将开始绘制前设置帧回调
setupFrameCallbacks();
...
}

最后就是示例了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testSync(SurfaceView surfaceView, ViewRootImpl viewRootImpl) {
SurfaceSyncGroup root = new SurfaceSyncGroup("root");
root.add(surfaceView, surfaceViewFrameCallback -> {
surfaceViewFrameCallback.onFrameStarted();
Canvas canvas = surfaceView.getHolder().lockCanvas();
// Draw something
surfaceView.getHolder().unlockCanvasAndPost(canvas);
});
root.add(viewRootImpl, () -> {
// Do something
});
root.add(mLayerRootImpl.getOrCreateSurfaceSyncGroup(), () -> {});
// Draw something to LayerRootImpl
root.markSyncReady();
}

看完是不是也觉得很简单。

作者

Hhvvg

发布于

2025-06-20

更新于

2025-06-21

许可协议