背景
参与系统开发的工程师应该都比较清楚,很多时候我们用 Surface
相关功能并不是使用 SurfaceView
的,而是自己创建一个 Surface
,这样 Surface
的可定制程度就可以很高。有的需求需要做到使 Transaction
跟 Surface
里面绘制的内容的位置等几何信息进行同步(例如 ViewRootImpl
本身也是直接使用 Surface
来绘制,也是自行实现了一套帧同步逻辑)。
但遗憾的是,Surface
本身只提供了绘制等基础功能,本身并不存在帧同步的实现。那么,我们可以参照 ViewRootImpl
以及 SurfaceView
的帧同步实现来给单独的 Surface
实现帧同步功能。
具体实现
要想实现帧同步,首先就要了解基础的帧同步原理。这个大体上是比较好理解的,主要分为以下几个流程。
- 监听帧可用回调
- 通过
BLASTBufferQueue
合并事务
上面两个流程已经是简化过的了,接下来通过代码来具体讲解是如何实现的。
初始化必要组件
首先,需要知道我们需要用到哪些类,Surface
、BLASTBufferQueue
是必不可少的两个,这两个是一开始创建 Surface
就创建好的,如果你不知道如何使用 BLASTBufferQueue
,可以参考 SurfaceView#createBlastSurfaceControls(ViewRootImpl, String, Transaction)
。
与 BLAST 相关的接口,除了 BLASTBufferQueue
除了能做帧同步外,通过 setBLASTLayer
来声明创建的 Surface
,可以做到背景模糊等功能。BLASTBufferQueue
也需要通过 BLAST Layer 来创建。
1 2 3 4 5 6 7 8 9 10 11
| public class LayerRootImpl {
private final Surface mSurface; private final BLASTBufferQueue mBLASTBufferQueue;
public LayerRootImpl(Surface surface, BLASTBufferQueue blastBufferQueue) { mSurface = surface; mBLASTBufferQueue = blastBufferQueue; } ... }
|
实现帧回调监听
如何实现帧回调监听呢?诶,框架刚好就实现了一个类 HardwareRenderer
来做这件事。
1 2 3 4 5 6 7
| ... private final HardwareRenderer mRenderer;
... mRenderer = new HardwareRenderer(); mRenderer.setSurface(surface); ...
|
然后可以通过 HardwareRenderer#setFrameCallback(HardwareRenderer.FrameDrawingCallback)
来注册帧回调监听了,是不是看着很简单。
但实际上,还有一个地方是需要注意的,就是什么时候去注册这个监听回调呢?先看下 BLASTBufferQueue
的 mergeWithNextTransaction
方法,有两个参数,一个是 Transaction
本身,一个是 frameNumber
,也就是需要同步的帧号。也就是说,我们是需要知道事务要具体跟哪一帧合并。但肯定不是跟过去的帧,而是跟未来要合成的帧,其实在大多数情况下都是需要下一帧去合并的(为什么是大多数呢?因为我也不知道会有哪些奇葩的需求)。
因此 setFrameCallback
这个接口肯定是需要在绘制前去调用的,这一点可以在 ThreadedRenderer#updateRootDisplayList(View, ThreadedRenderer.DrawCallbacks)
处求证,没错,我们熟知的 ViewRootImpl
的帧同步也是通过这个方法实现的。
在这里,我们可以仿照 Surface
,创建一个 HwuiContext
类,来维护管理 Canvas
的生成。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| private final class HwuiContext {
private final RenderNode mRenderNode;
private HardwareRenderer mHardwareRenderer;
private RecordingCanvas mCanvas;
private List<HardwareRenderer.FrameDrawingCallback> mCallbacks;
HwuiContext() { mRenderNode = RenderNode.create("HwuiCanvas", null); mRenderNode.setClipToBounds(false); mRenderNode.setForceDarkAllowed(false);
mHardwareRenderer = new HardwareRenderer(); mHardwareRenderer.setContentRoot(mRenderNode); mHardwareRenderer.setSurface(mSurface, true); mHardwareRenderer.setLightSourceAlpha(0.0f, 0.0f); mHardwareRenderer.setLightSourceGeometry(0.0f, 0.0f, 0.0f, 0.0f); }
Canvas lockCanvas(int width, int height) { if (mCanvas != null) { throw new IllegalStateException("Surface was already locked!"); } mCanvas = mRenderNode.beginRecording(width, height); return mCanvas; }
void unlockAndPost(Canvas canvas) { if (canvas != mCanvas) { throw new IllegalArgumentException("canvas object must be the same instance that " + "was previously returned by lockCanvas"); } mRenderNode.endRecording(); mCanvas = null; setupFrameCallbacks(); mHardwareRenderer.createRenderRequest() .setVsyncTime(System.nanoTime()) .syncAndDraw(); }
private void setupFrameCallbacks() { List<HardwareRenderer.FrameDrawingCallback> frameCallbacks = mCallbacks; mCallbacks = null; if (frameCallbacks == null || frameCallbacks.isEmpty()) { return; } mHardwareRenderer.setFrameCallback(new HardwareRenderer.FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { }
@Override public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) { ArrayList<HardwareRenderer.FrameCommitCallback> frameCommitCallbacks = new ArrayList<>(); for (int i = 0; i < frameCallbacks.size(); ++i) { HardwareRenderer.FrameCommitCallback frameCommitCallback = frameCallbacks.get(i).onFrameDraw(syncResult, frame); if (frameCommitCallback != null) { frameCommitCallbacks.add(frameCommitCallback); } }
if (frameCommitCallbacks.isEmpty()) { return null; }
return didProduceBuffer -> { for (int i = 0; i < frameCommitCallbacks.size(); ++i) { frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer); } }; } }); }
void destroy() { mHardwareRenderer.destroy(); } }
|
然后对外暴露注册帧回调的接口:
1 2 3 4 5 6
| public void registerRtFrameCallback(HardwareRenderer.FrameDrawingCallback callback) { if (mHwuiContext.mCallbacks == null) { mHwuiContext.mCallbacks = new ArrayList<>(); } mHwuiContext.mCallbacks.add(callback); }
|
以及向 BLASTBufferQueue
合并事务的接口:
1 2 3
| public void mergeWithNextTransaction(SurfaceControl.Transaction t, long frameNumber) { mBLASTBufferQueue.mergeWithNextTransaction(t, frameNumber); }
|
绘制以及监听
经过上面的步骤之后其实以及几乎完工了,最后就是使用示例而已了。
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
| public class LayerRootImplTest { private final SurfaceControl mSurfaceControl; private final SurfaceControl mBlastSurfaceControl; private final BLASTBufferQueue mBlastBufferQueue; private final Surface mSurface; private final LayerRootImpl mLayerRootImpl;
public LayerRootImplTest(View rootView, int width, int height, int format) { SurfaceSession session = new SurfaceSession(); mSurfaceControl = new SurfaceControl.Builder(session) .setName("test-surface") .setParent(rootView.getViewRootImpl().getSurfaceControl()) .setCallsite("SurfaceView.updateSurface") .setContainerLayer() .build(); mBlastSurfaceControl = new SurfaceControl.Builder(session) .setName("test-surface(BLAST)") .setParent(mSurfaceControl) .setHidden(false) .setBLASTLayer() .setCallsite("SurfaceView.updateSurface") .build(); mBlastBufferQueue = new BLASTBufferQueue("test-blast-queue", false ); mBlastBufferQueue.update(mBlastSurfaceControl, width, height, format); mSurface = new Surface(); mSurface.copyFrom(mBlastBufferQueue); mLayerRootImpl = new LayerRootImpl(mSurface, mBlastBufferQueue); }
public void testSync(SurfaceControl.Transaction t) { mLayerRootImpl.registerRtFrameCallback(frame -> mLayerRootImpl.mergeWithNextTransaction(t, frame)); } }
|
出乎意料的简单。
完整代码
LayerRootImpl
:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| package com.example;
import android.graphics.BLASTBufferQueue; import android.graphics.Canvas; import android.graphics.HardwareRenderer; import android.graphics.RecordingCanvas; import android.graphics.RenderNode; import android.view.Surface; import android.view.SurfaceControl;
import java.util.ArrayList; import java.util.List;
public class LayerRootImpl {
private final Surface mSurface; private final BLASTBufferQueue mBLASTBufferQueue; private final HwuiContext mHwuiContext;
public LayerRootImpl(Surface surface, BLASTBufferQueue blastBufferQueue) { mSurface = surface; mBLASTBufferQueue = blastBufferQueue; mHwuiContext = new HwuiContext(); }
public void registerRtFrameCallback(HardwareRenderer.FrameDrawingCallback callback) { if (mHwuiContext.mCallbacks == null) { mHwuiContext.mCallbacks = new ArrayList<>(); } mHwuiContext.mCallbacks.add(callback); }
public void mergeWithNextTransaction(SurfaceControl.Transaction t, long frameNumber) { mBLASTBufferQueue.mergeWithNextTransaction(t, frameNumber); }
private final class HwuiContext {
private final RenderNode mRenderNode;
private final HardwareRenderer mHardwareRenderer;
private RecordingCanvas mCanvas;
private List<HardwareRenderer.FrameDrawingCallback> mCallbacks;
HwuiContext() { mRenderNode = RenderNode.create("HwuiCanvas", null); mRenderNode.setClipToBounds(false); mRenderNode.setForceDarkAllowed(false);
mHardwareRenderer = new HardwareRenderer(); mHardwareRenderer.setContentRoot(mRenderNode); mHardwareRenderer.setSurface(mSurface, true); mHardwareRenderer.setLightSourceAlpha(0.0f, 0.0f); mHardwareRenderer.setLightSourceGeometry(0.0f, 0.0f, 0.0f, 0.0f); }
Canvas lockCanvas(int width, int height) { if (mCanvas != null) { throw new IllegalStateException("Surface was already locked!"); } mCanvas = mRenderNode.beginRecording(width, height); return mCanvas; }
void unlockAndPost(Canvas canvas) { if (canvas != mCanvas) { throw new IllegalArgumentException("canvas object must be the same instance that " + "was previously returned by lockCanvas"); } mRenderNode.endRecording(); mCanvas = null; setupFrameCallbacks(); mHardwareRenderer.createRenderRequest() .setVsyncTime(System.nanoTime()) .syncAndDraw(); }
private void setupFrameCallbacks() { List<HardwareRenderer.FrameDrawingCallback> frameCallbacks = mCallbacks; mCallbacks = null; if (frameCallbacks == null || frameCallbacks.isEmpty()) { return; } mHardwareRenderer.setFrameCallback(new HardwareRenderer.FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { }
@Override public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) { ArrayList<HardwareRenderer.FrameCommitCallback> frameCommitCallbacks = new ArrayList<>(); for (int i = 0; i < frameCallbacks.size(); ++i) { HardwareRenderer.FrameCommitCallback frameCommitCallback = frameCallbacks.get(i).onFrameDraw(syncResult, frame); if (frameCommitCallback != null) { frameCommitCallbacks.add(frameCommitCallback); } }
if (frameCommitCallbacks.isEmpty()) { return null; }
return didProduceBuffer -> { for (int i = 0; i < frameCommitCallbacks.size(); ++i) { frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer); } }; } }); }
void destroy() { mHardwareRenderer.destroy(); } } }
|
LayerRootImplTest
:
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 46
| package com.example;
import android.graphics.BLASTBufferQueue; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View;
public class LayerRootImplTest { private final SurfaceControl mSurfaceControl; private final SurfaceControl mBlastSurfaceControl; private final BLASTBufferQueue mBlastBufferQueue; private final Surface mSurface; private final LayerRootImpl mLayerRootImpl;
public LayerRootImplTest(View rootView, int width, int height, int format) { SurfaceSession session = new SurfaceSession(); mSurfaceControl = new SurfaceControl.Builder(session) .setName("test-surface") .setParent(rootView.getViewRootImpl().getSurfaceControl()) .setCallsite("SurfaceView.updateSurface") .setContainerLayer() .build(); mBlastSurfaceControl = new SurfaceControl.Builder(session) .setName("test-surface(BLAST)") .setParent(mSurfaceControl) .setHidden(false) .setBLASTLayer() .setCallsite("SurfaceView.updateSurface") .build(); mBlastBufferQueue = new BLASTBufferQueue("test-blast-queue", false ); mBlastBufferQueue.update(mBlastSurfaceControl, width, height, format); mSurface = new Surface(); mSurface.copyFrom(mBlastBufferQueue); mLayerRootImpl = new LayerRootImpl(mSurface, mBlastBufferQueue); }
public void testSync(SurfaceControl.Transaction t) { mLayerRootImpl.registerRtFrameCallback(frame -> mLayerRootImpl.mergeWithNextTransaction(t, frame)); } }
|