背景 我的上一篇文章 讲到了给 Surface
实现帧同步,作为实现帧同步的重要手段,在做动画需求,特别是跨进程同步渲染的需求中发挥了重要的作用。但还有另一种跨进程渲染的方案,在 Android13 被提了出来,这便是 SurfaceSyncGroup
。SurfaceSyncGroup
是用来替代 SurfaceSyncer
的方案的,SurfaceSyncer
我没用过(Android13 发布的时候我刚工作一年),所以就不提了,不过值得注意的是,这个用来帧同步的 SurfaceSyncGroup
居然是作为开放 API
发布的,难得见到谷歌慷慨了一回啊,之前图形绘制相关的 API
,不管是 BLAST
相关还是 SurfaceSyncer
都是标明了 hide 接口的。
所以我为什么老是需要用到帧同步这些东西呢,因为我在去年年末的时候接手了桌面的窗口动画业务,特别是在后续的需求中引入多个 Surface
动画之后,有些场景并不能单纯依靠事务。比如绘制的同步就不能依靠我上一篇文章中的那种方法,这就涉及到 Surface
绘制的原理了,绘制是一种隐式的事务,在绘制操作从主线程转移到 RenderThread
上帧渲染完成之后,缓冲区就会挂到一个 Transaction
上,然后这个 Transaction
被发送到 SurfaceFlinger
去进行合成。这点可以通过 Winscope
来验证,在 Windscope
的 Transcation
一栏,是可以找到带有 Buffer
的 Transaction
的。
关于 SurfaceSyncGroup
的原理,网上一搜讲解很多,我这里就不详细展开了,简单来说就是通过一个根 group
,来管理所有的子 group
。对于这个根 group
,如果是本地进程就由本地进程管理,如果是跨进程则通过 WMS
来管理。每个子 group
在绘制完成之后需要调用 markSyncReady
来通知根 group
,然后根 group
会检查是否所有的子 group
已经全部准备好,如果全部准备好了,就调用 Transaction#apply
来应用事务。那怎么做到同步帧缓冲的呢?想起我上面说的了吗,绘制也是一种隐式的事务,最终也是要转为事务来应用的,只不过对于开发者来说是个黑箱,无感知而已。
那么问题来了,既然要做绘制同步,我们肯定是要拦截掉带有绘制 Buffer
的 Transaction
的,该怎么做到的呢?诶,这时候就要请出我们万能的 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 = new SurfaceSyncGroup (surfaceView.getName()); if (add(surfaceSyncGroup.mISurfaceSyncGroup, false , null )) { frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> { surfaceSyncGroup.addTransaction(transaction); surfaceSyncGroup.markSyncReady(); })); return true ; } return false ; }
上面的重点就是 SurfaceView
的 syncNextFrame
方法,这个方法最终调用到了 BLASTBufferQueue#syncNextTransaction(Consumer)
方法, 最终终于到我们的主角出场了,看下这个方法的注释是怎样的吧:
1 2 3 4 5 6 7 8 9 10 public boolean syncNextTransaction (@NonNull Consumer<SurfaceControl.Transaction> callback) { return syncNextTransaction(true , callback); }
看到了吧,说是会回调带有 Buffer
的 Transaction
给 callback
。那这下就好办了,我们照猫画虎就可以了。
实现 首先需要声明当前活跃的 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) { 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(); }); if (!result) { activeSyncGroup.markSyncReady(); } return didProduceBuffer -> { if (!didProduceBuffer) { mBLASTBufferQueue.clearSyncTransaction(); activeSyncGroup.addTransaction( mBLASTBufferQueue.gatherPendingTransactions(frame)); activeSyncGroup.markSyncReady(); } }; } }); }
最后,在 HwuiContext
的 unlockAndPost
方法中,注册该回调,注意是要在绘制开始前注册的。
1 2 3 4 5 6 7 8 void unlockAndPost (Canvas canvas) { ... 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(); surfaceView.getHolder().unlockCanvasAndPost(canvas); }); root.add(viewRootImpl, () -> { }); root.add(mLayerRootImpl.getOrCreateSurfaceSyncGroup(), () -> {}); root.markSyncReady(); }
看完是不是也觉得很简单。