Flutter中的Tree(四) LayerTree

什么是Layer?

在前一篇文章中我们说到, Layer可以理解成Flutter渲染的一次”操作”. Flutter Framework通过SceneBuilder来将Layer所抽象的渲染操作映射到Engine层. 一系列操作的结果在Framework层被叫做Scene. Scene生成后, 就会被送到GPU进行光栅化, 最终显示在屏幕上.

SceneBuilder将Engine层的绘图API提供给了Framework层, 同时维护了一个堆栈. 当堆栈被push了一个Layer时, Engine就会进行一次Layer所代表的”操作”; 当堆栈pop时, Engine就会撤销最近的一次”操作”.

Layer的分类

上图是Layer的继承树. 我们依次来介绍一下Layer的几个子类.

ContainerLayer

ContainerLayer是唯一一类可以持有child的Layer. 正因为有这一类Layer, Layer Tree才得以存在.

ContainerLayer只有构建Layer Tree这一个功能, 本身并不代表某种”操作”, 所以实际上并不怎么用到. 但是我们会用到它的各种子类: OffsetLayer, ClipRectLayer等. 这些子类除了持有child, 构成Layer Tree外, 还代表着一种”操作”. 比如OffsetLayer代表位移操作, ClipRectLayer代表剪切操作, OpacityLayer代表设置透明度操作等.

PictureLayer

PictureLayer, 理所当然地, 代表的就是画图的操作了. RenderObject在进行paint时会把自己绘制到最近的RepaintBoundary提供的PictureLayer上, 所以当PictureLayer被送入SceneBuilder时, 它所描述的画面也就被绘制完成了.

顺便说一句, 上一章中我们提到了RepaintBoundary的概念. 当一个RenderObject被声明为RepaintBoundary时, Framework会给它设置一个OffsetLayer. 通过这个Layer, 我们可以遍历到所有以它为根节点的子Layer树的PictureLayer节点. 所以通过一个RepaintBoundary, 我们一定能画出一张以它为根节点的Layer Tree所描述的画面. 这就是Flutter通过RepaintBoundary实现屏幕截图的原理.

TextureLayer

TextureLayer代表的操作是绘制外接纹理(Backend Texture), 实际上就是来自Native的画面. 我们都知道APP的纯画面都是RenderObject描述, PictureLayer生成的. 但像照相机, 视频播放器这些画面, Flutter是没有对应的RenderObject来描述的(也不可能有, 毕竟绘制它们的是Native). 但是这些画面还得显示呀, 所以就定义了TextureLayer这么一个类. 它代表的操作就是把来自Native的画面投射到Flutter View的指定区域里显示.

PlatformViewLayer

PlatformViewLayer, 顾名思义是给PlatformView用的Layer. PlatformView, 其实就是AndroidView和UiKitVIew这两个Widget. 这两个Widget是Android和iOS原生View的映射. 也就是说, 通过这两个Widget, 我们可以在Flutter APP用Native来绘制组件, 同时还可以像Widget一样来管理这些原生绘制的组件. Texture和PlatformView的区别在于, Texture只是在渲染的时候, 把Native的画面画在了Flutter View上, Flutter本身是不知道画面的属性的; 而PlatformView则是把属性也暴露给了Flutter这里.

PerformanceOverlayLayer

PerformanceOverlay, 当然就是绘制PerformanceOverLay的Layer了. 什么是PerformanceLayer? 就是Flutter自带的显示GPU和CPU每帧的的表现的一个性能工具. 我们可以通过设置WidgetsApp, MaterialApp和CupertinoApp(本质上还是设置WidgetsApp)的showPerformanceLayer来启用工具.

Layer的核心成员变量和方法

_engineLayer和_parentHandle

每一个Layer都有一个EngineLayer类型的_engineLayer变量. 每一类Layer都有对应的EngineLayer, 即”操作”在Engine层的实现. 因为Layer是持有EngineLayer的, 而EngineLayer作为Engine层的实现, 一定是持有了底层资源的, 所以不用的Layer一定要及时被GC, 这样Engine层的资源才会被释放. 但是毕竟不是所有的Layer每一帧都完全不同的的, 为了提高性能, 我们又需要留着没有改变的Layer直接用来绘制. 所以, Layer加入了一个LayerHandle类型的_parentHandle变量, 实际上就是个引用计数器. 当这个Layer成为某个Layer的child的时候, 计数器就会加一; 当Layer不再成为某个Layer的child的时候, 计数器就会减一. 只有引用计数为0的Layer才会在GC时被回收.

addToScene()

前面我们说了, Layer被push进SceneBuilder的堆栈里是, 所代表的操作才会被执行. 加入堆栈的过程就是通过Layer的addToScene(SceneBuilder builder)启动的.

_needsAddToScene和alwaysNeedsAddToScene

我们知道, Layer被addToScene一次, 就会被重新绘制一次. 我们还知道, 为了提高性能, 每次绘制时应该只绘制需要绘制的Layer. 那么怎么区分Layer是否要绘制呢? Layer定义了_needsAddToScene和alwaysNeedsAddToScene两个标记. 当alwaysNeedsAddToScene被设置为true时, Layer会参与每次的绘制, 即使它没有变化. 对应的, 当alwaysNeedsAddToScene被设置为false时, Layer是否参与绘制就要通过_needsAddToScene来判断了. _needsAddToScene在下面两个情况时会被设置成true:

  1. 自身发生了变化, 如PictureLayer的picture、TransformLayer的transform被重新赋值

  2. 子节点有增删

完成绘制后, _needsAddToScene会被重置为false.