本文是
Flutter Framework 渲染流程分析中的第四篇
- Flutter Framework 渲染流程分析(一):开篇
- Flutter Framework 渲染流程分析(二):Element
- Flutter Framework 渲染流程分析(三):RenderObject
- Flutter Framework 渲染流程分析(四):Layer
- Flutter Framework 渲染流程分析(五):常见问题分析
上篇我们讲了RenderObject中layout和paint流程。layout最终计算出来的布局信息是提供给paint使用的,paint的flush阶段会调用到自身的void paint(PaintingContext context, Offset offset)方法,我们来回顾下这个方法。
dart
/// code 4.1
///[RenderClipOval] 中的实现
void paint(PaintingContext context, Offset offset) {
if (child != null) {
if (clipBehavior != Clip.none) {
_updateClip();
layer = context.pushClipPath(
needsCompositing,
offset,
_clip,
_getClipPath(_clip!),
super.paint,
clipBehavior: clipBehavior,
oldLayer: layer as ClipPathLayer?,
);
} else {
context.paintChild(child!, offset);
layer = null;
}
} else {
layer = null;
}
}
这里引入了两个重要的对象,一个是Layer,一个是PaintingContext。_clip 是一个 Rect对象,context.pushClipPath通过_clip做layer的裁剪,得到的新图层重新赋值给layer。先看一下RenderObject中关于layer的定义。
RenderObject 中的 Layer
dart
/// code 4.2
/// [RenderObejct] 中的代码
@protected
ContainerLayer? get layer {
assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layerHandle.layer;
}
@protected
set layer(ContainerLayer? newLayer) {
assert(
!isRepaintBoundary,
'Attempted to set a layer to a repaint boundary render object.\n'
'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.',
);
_layerHandle.layer = newLayer;
}
final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();
对layer的操作都会转到_layerHandle上来,LayerHandle的接口比较简单,看下面set layer(T? layer)方法的实现,它只是持有layer并对layer做引用计数而已,也就是这个layer被多少个LayerHandle持有了,当持有数为 0 时,_layer?_unref()里就会对layer做资源释放工作。
dart
/// code 4.3
class LayerHandle<T extends Layer> {
/// Create a new layer handle, optionally referencing a [Layer].
LayerHandle([this._layer]) {
if (_layer != null) {
_layer!._refCount += 1;
}
}
T? _layer;
/// The [Layer] whose resources this object keeps alive.
///
/// Setting a new value or null will dispose the previously held layer if
/// there are no other open handles to that layer.
T? get layer => _layer;
set layer(T? layer) {
assert(
layer?.debugDisposed != true,
'Attempted to create a handle to an already disposed layer: $layer.',
);
if (identical(layer, _layer)) {
return;
}
_layer?._unref();
_layer = layer;
if (_layer != null) {
_layer!._refCount += 1;
}
}
@override
String toString() => 'LayerHandle(${_layer != null ? _layer.toString() : 'DISPOSED'})';
}
对Layer先暂时了解这么多,再看PaintingContext。
PaintingContext
dart
/// code 4.4
class PaintingContext extends ClipContext {
/// Creates a painting context.
///
/// Typically only called by [PaintingContext.repaintCompositedChild]
/// and [pushLayer].
@protected
PaintingContext(this._containerLayer, this.estimatedBounds);
final ContainerLayer _containerLayer;
/// An estimate of the bounds within which the painting context's [canvas]
/// will record painting commands. This can be useful for debugging.
///
/// The canvas will allow painting outside these bounds.
///
/// The [estimatedBounds] rectangle is in the [canvas] coordinate system.
final Rect estimatedBounds;
...
// 一系列 layer 操作
ClipRectLayer? pushClipRect(...) ...
ClipRRectLayer? pushClipRRect(...) ...
ClipPathLayer? pushClipPath(...) ...
ColorFilterLayer pushColorFilter(...) ...
TransformLayer? pushTransform(...) ...
OpacityLayer pushOpacity(...) ...
...
}
PaintingContext是绘制上下文,包含一系列 layer 操作,构造函数接受两个参数,一个是_containerLayer,一个是estimatedBounds。estimatedBounds限制了图层的位置和大小,ContainerLayer是Layer的一种,从名字就猜到它是用来组合Layer的。根据code 4.1中的代码,看一下pushClipPath的实现
dart
/// code 4.5
ClipPathLayer? pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipPathLayer? oldLayer }) {
if (clipBehavior == Clip.none) {
painter(this, offset);
return null;
}
final Rect offsetBounds = bounds.shift(offset);
final Path offsetClipPath = clipPath.shift(offset);
// 这里我们着重看这条件分支里面的处理
if (needsCompositing) {
final ClipPathLayer layer = oldLayer ?? ClipPathLayer();
layer
..clipPath = offsetClipPath
..clipBehavior = clipBehavior;
// 构造一个新的 layer,push 到 _containerLayer 上
pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
return layer;
} else {
// 通过 canvas 操作将裁剪操作保存到 _currentLayer 上
clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
return null;
}
}
// super.paint 的实现
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.paintChild(child!, offset);
}
}
先明确一下,这个方法里面有一个方法传参painter,根据 code 4.1 中的代码,就是super.paint,看它的实现调用[PaintingContext].paintChild的操作,其实就是绘制子节点,无论是pushLayer,还是clipPathAndPaint,都将这个painter操作传进去了。
pushLayer
dart
/// code 4.6
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) {
...
appendLayer(childLayer);
// createContext 实现如下面的代码,创建一个新的绘制上下文,此时的 childLayer 是上面传的 ClipPathLayer
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
// 执行 child 的绘制
painter(childContext, offset);
...
}
@protected
void appendLayer(Layer layer) {
...
// 移除 layer 原先的 parent(如果存在)
layer.remove();
// 加到 _containerLayer
_containerLayer.append(layer);
}
@protected
PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
return PaintingContext(childLayer, bounds);
childLayer是code 4.5中传进来的ClipPathLayer,已经限制了裁剪区域。pushLayer在把childLayer添加到_containerLayer之后,同时将它传递到childContext去绘制,这样就限制了child绘制和区域,实现裁剪的功能。
clipPathAndRepaint
dart
/// code 4.7
void _clipAndPaint(void Function(bool doAntiAlias) canvasClipCall, Clip clipBehavior, Rect bounds, VoidCallback painter) {
// canvas 操作
canvas.save();
switch (clipBehavior) {
case Clip.none:
break;
case Clip.hardEdge:
canvasClipCall(false);
case Clip.antiAlias:
canvasClipCall(true);
case Clip.antiAliasWithSaveLayer:
canvasClipCall(true);
canvas.saveLayer(bounds, Paint());
}
// painter 实际上就是 childContext.paint
painter();
if (clipBehavior == Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
canvas.restore();
}
void clipPathAndPaint(Path path, Clip clipBehavior, Rect bounds, VoidCallback painter) {
// 执行 canvas 的裁剪操作
_clipAndPaint((bool doAntiAlias) => canvas.clipPath(path, doAntiAlias: doAntiAlias), clipBehavior, bounds, painter);
}
根据 clipBehavior,canvas 先做了一个clipPath的裁剪操作,然后再绘制 child。canvas 操作最终是会反映到_currentLayer上的,看下面这段代码:
dart
/// code 4.8
bool get _isRecording {
// 开始记录时,_canvas会被初始化
final bool hasCanvas = _canvas != null;
...
return hasCanvas;
}
// Recording state
PictureLayer? _currentLayer;
ui.PictureRecorder? _recorder;
Canvas? _canvas;
@override
Canvas get canvas {
if (_canvas == null) {
_startRecording();
}
assert(_currentLayer != null);
return _canvas!;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder!);
_containerLayer.append(_currentLayer!);
}
@protected
@mustCallSuper
void stopRecordingIfNeeded() {
if (!_isRecording) {
return;
}
...
_currentLayer!.picture = _recorder!.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
先看_startRecording方法,开始记录绘制时,_canvas会被初始化,同时会传入_recorder用来记录任务在_canvas上执行的操作,最终在stopRecordingIfNeeded时,通过recorder!.endRecording()将之前记录的绘制动作输出成一个picture,然后赋值给_currentLayer!.picture,注意_currentLayer是在_startRecording时就已经append到_containerLayer上的。
无论是pushLayer还是clipPathAndRepaint,其绘制结果都存在于_containerLayer上,同时这个_containerLayer是在 PaintingContext 实例化时由外部提供。还记得我们 code 4.5 中说到的绘制子节点的方法painter,它最终会走到[PaintContext].paintChild这里
dart
/// code 4.9
void paintChild(RenderObject child, Offset offset) {
...
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
// 这里会生成一个新的 layer,用来绘制 child
// _compositeChild 方法也会根据 _needPaint 这个标记去判断是不是要重新生成
_compositeChild(child, offset);
} else if (child._wasRepaintBoundary) {
child._layerHandle.layer = null;
child._paintWithContext(this, offset);
} else {
child._paintWithContext(this, offset);
}
}
如果是绘制边界,那么新建一个PaintContext去绘制;否则将其绘制到当前的PaintingContext上。
从上面的绘制操作可以看出,每一个节点在绘制时,都要绘制子节点;根据isRepaintBoundary判断要不要生成一个独立的绘制区域,再根据_needsPaint判断是不是要新建一个PaintingContext,这样就能保证每次绘制时只绘制脏区域。
另外,PaintContext里面针对 child layer的处理完成后,最终都会走到 code 4.6 中的appendLayer方法里,这个方法会走到_containerLayer.append,形成Layer Tree。
那么顶层layer是哪里来的?这就回归到RenderObject中关于layer的传递了,RenderObject持有着一个LayerHandle,而LayerHandle里layer属性默认是null的。上篇我们讲过RenderObject的继承关系,根RenderObject是RenderView,通过追踪一下RenderView的实例化,就能找到在RenderView._updateMatricesAndCreateNewRootLayer时生成的rootLayer。
dart
/// code 4.10
void prepareInitialFrame() {
...
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
...
}
void scheduleInitialPaint(ContainerLayer rootLayer) {
...
// 设置 layer
_layerHandle.layer = rootLayer;
...
owner!._nodesNeedingPaint.add(this);
}
Matrix4? _rootTransform;
TransformLayer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
...
return rootLayer;
}
root RenderObject 是RenderView,root Layer 是TransformLayer。rootLayer.attach到当前RenderView上时,也会将RenderView传递到所有child layer上,也就是所有layer的owner都会是同一个RenderView。
现在我们能拿到rootLayer了,只是rootLayer怎么去绘制到界面上还不清楚。实际上对于layer这个对象我们目前还一知半解,只知道它里面可能保存着绘制信息。跟前几篇一样,还是从对象的关键属性和继承关系着手看
Layer
dart
/// code 4.11
// 1. 如 code 4.3 中提到的,主要是用来配合 LayerHandle 做引用计数和资源释放
int _refCount = 0;
void _unref(){};
// 2. 继承自 AbstractNode 的接口,构成树形结构。从 parent 的返回可以断定每一个节点都是或继承自ContainerLayer
ContainerLayer? get parent => super.parent as ContainerLayer?;
void dropChild(Layer child){};
void adoptChild(Layer child){};
// 3. 兄弟节点双向链接结构,在 append 时会处理这个兄弟节点的关系
Layer? get nextSibling => _nextSibling;
Layer? _nextSibling;
Layer? get previousSibling => _previousSibling;
Layer? _previousSibling;
// 前面提到的节点的关系的形成就在这个方法里面处理
void append(Layer child){};
// 4. 标记这个 Layer 是否已经更新过,标志的节点会在随后走到 addToScene 方法
bool _needsAddToScene = true;
void markNeedsAddToScene() {};
bool get alwaysNeedsAddToScene => false;
// 5. addToScene 是将当前 Layer 中绘制信息通过 builder 推送到 Engine 上绘制
// 生成的绘制信息保存到 _engineLayer 上。
void addToScene(ui.SceneBuilder builder);
vid _addToSceneWithRetainedRendering(ui.SceneBuilder builder){};
// 6. 如 5 中提到的,_engineLayer 是当前 layer 在 Engine 中的映射对象。
ui.EngineLayer? get engineLayer => _engineLayer;
set engineLayer(ui.EngineLayer? value) {};
ui.EngineLayer? _engineLayer;
这段代码比较长,但总的来看 1、2、3 点都是为了形成Layer Tree的;4、5、6 点是跟绘制有关的,_enginelayer 就是这个Layer的绘制结果,它是 Layer 在 Engine 中的映射。在它的子类上,会保存着一些绘制的信息,比如下面这段在ClipRectLayer中的代码
dart
// code 4.11
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
...
if (!_needsAddToScene && _engineLayer != null) {
// 节点有更新时会走 addRetained,就是会把上次的 _engineLayer 推送到 Engine 中渲染
builder.addRetained(_engineLayer!);
return;
}
// 否则从新生成 _engineLayer,看以下的实现
addToScene(builder);
...
_needsAddToScene = false;
}
// Layer 中的 addToScene 是空实现,看子类 ClipRectLayer 的
void addToScene(ui.SceneBuilder builder) {
...
bool enabled = true;
...
if (enabled) {
engineLayer = builder.pushClipRect(
clipRect!,
clipBehavior: clipBehavior,
oldLayer: _engineLayer as ui.ClipRectEngineLayer?,
);
} else {
engineLayer = null;
}
// 绘制子节点,这里是一个递归处理,会更新底下所有的子孙节点的 Layer
addChildrenToScene(builder);
if (enabled) {
builder.pop();
}
}
对于ClipRectLayer来说,它的绘制信息就是clipRect和clipBehavior,然后通过ui.SceneBuilder中的pushClipRect调用Engine中相应的操作方法生成EngineLayer并保存下来。ui.SceneBuilder和ui.EngineLayer都是Engine对象。
每一个Layer的addToScene都会触发底下所有子孙节点的更新,子孙节点或是命中缓存处理builder.addRetained,或是走重新添加addToScene。不管哪种方式,最终都是利用ui.SceneBuilder去跟Engine层做交互,ui.SceneBulider实例化在RenderView中。
dart
// code 4.12
// [RenderView] 中的方法
void compositeFrame() {
...
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer!.buildScene(builder);
if (automaticSystemUiAdjustment) {
_updateSystemChrome();
}
// _view 是 ui.FlutterView,用来绘制界面的而已。render 也对应着 Engine 中的方法。
_view.render(scene);
scene.dispose();
...
}
...
}
// ContainerLayer 中的 buildScene 方法
ui.Scene buildScene(ui.SceneBuilder builder) {
updateSubtreeNeedsAddToScene();
// 这里即是更新 layer tree,builder 会保存有 layer tree 的信息
addToScene(builder);
...
_needsAddToScene = false;
// 生成 ui.Scene,返回给 _view 最终渲染到界面上
final ui.Scene scene = builder.build();
return scene;
}
ui.SceneBuilder会根据layer tree的信息生成ui.Scene,通过ui.FlutterView最终将ui.Scene渲染到界面上。渲染方法是_view.render,它接受一个ui.Scene对象,而ui.Scene是通过ui.SceneBuilder根据layer tree生成的。
总结
本文讲了跟Paint流程有关的几个重要的概念:Layer :存储着绘制信息的对象,同时持有着 Engine 层的绘制结果 EngineLayer;PaintingContext :绘制上下文,处理 Layer 的操作,构建Layer Tree,同时最后也讲了关于形成的Layer Tree最终是怎么通知到Engine中进行绘制的。
到现在,Widget、Element、RenderObject、Layer,包括Layout、Paint流程相关的东西都讲完了,最后一篇文章会根据前面积累的这些知识点,重头梳理一下整个渲染流程,从首帧渲染到我们日常开发中比较关注的部分,做一个完整性补充。