ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## drawFrame ~~~ void drawFrame() { pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); renderView.compositeFrame(); // this sends the bits to the GPU pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. } ~~~ `pipelineOwner.flushLayout()`:调用完成以后渲染流水线就进入了绘制(paint)阶段. `pipelineOwner.flushCompositingBits()`:更新render tree 中`RenderObject`的`_needsCompositing`标志位的。 `pipelineOwner.flushPaint()`: 完成绘制工作 `renderView.flushPaint()`: 把整个layer tree生成`scene`送到engine去显示。 ## flushCompositingBits `flushCompositingBits()`源码如下: ~~~ void flushCompositingBits() { _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth); for (RenderObject node in _nodesNeedingCompositingBitsUpdate) { if (node._needsCompositingBitsUpdate && node.owner == this) node._updateCompositingBits(); } _nodesNeedingCompositingBitsUpdate.clear(); } ~~~ 回头看flushCompositingBits函数,主要做的事情就是: 1. 把列表`_nodesNeedingCompositingBitsUpdate`按照节点在树中的深度排序。 2. 遍历调用`node._updateCompositingBits()` ### _updateCompositingBits ~~~ void _updateCompositingBits() { if (!_needsCompositingBitsUpdate) return; final bool oldNeedsCompositing = _needsCompositing; _needsCompositing = false; visitChildren((RenderObject child) { child._updateCompositingBits(); if (child.needsCompositing) _needsCompositing = true; }); if (isRepaintBoundary || alwaysNeedsCompositing) _needsCompositing = true; if (oldNeedsCompositing != _needsCompositing) markNeedsPaint(); _needsCompositingBitsUpdate = false; } ~~~ _needsCompositing设成true两种个情况: 1. 从当前节点往下找,如果某个子节点`_needsCompositing`为`true` 2. 当前子节点`isRepaintBoundary`为`true`或`alwaysNeedsCompositing`为`true` 如果`_needsCompositing`发生了变化,那么会调用`markNeedsPaint()`通知渲染流水线本`RenderObject`需要重绘了 ### `RenderObject`的标志位 * `bool _needsCompositing`:标志自身或者某个孩子节点有合成层(compositing layer)。如果当前节点需要合成,那么所有祖先节点也都需要合成。 * `bool _needsCompositingBitsUpdate`:标志当前节点是否需要更新`_needsCompositing`。这个标志位由下方的`markNeedsCompositingBitsUpdate()`函数设置。 * `bool get isRepaintBoundary => false;`:标志当前节点是否与父节点分开来重绘。当这个标志位为`true`的时候,父节点重绘的时候子节点不一定也需要重绘,同样的,当自身重绘的时候父节点不一定需要重绘。此标志位为`true`的`RenderObject`有render tree的根节点`RenderView`,有我们熟悉的`RenderRepaintBoundary`,`TextureBox`等。 * `bool get alwaysNeedsCompositing => false;`:标志当前节点是否总是需要合成。这个标志位为`true`的话意味着当前节点绘制的时候总是会新开合成层(composited layer)。例如`TextureBox`, 以及我们熟悉的显示运行时性能的`RenderPerformanceOverlay`等。 ### markNeedsCompositingBitsUpdate 在渲染流水线的构建阶段,有些情况下render tree里的节点需要重新更新`_needsCompositing`,比如说render tree里节点的增加,删除。这个标记工作由函数`markNeedsCompositingBitsUpdate()`完成。 ~~~ void markNeedsCompositingBitsUpdate() { if (_needsCompositingBitsUpdate) return; _needsCompositingBitsUpdate = true; if (parent is RenderObject) { final RenderObject parent = this.parent; if (parent._needsCompositingBitsUpdate) return; if (!isRepaintBoundary && !parent.isRepaintBoundary) { parent.markNeedsCompositingBitsUpdate(); return; } } if (owner != null) owner._nodesNeedingCompositingBitsUpdate.add(this); } ~~~ 这个调用会从当前节点往上找,把所有父节点的`_needsCompositingBitsUpdate`标志位都置位`true`。直到自己或者父节点的`isRepaintBoundary`为`true`。最后会把自己加入到`PipelineOwner`的`_nodesNeedingCompositingBitsUpdate`列表里面。而函数调用`pipelineOwner.flushCompositingBits()`正是用来处理这个列表的。 ## flushPaint ### markNeedsPaint 当某个`RenderObject`需要被重绘的时候会调用`markNeedsPaint()` ~~~ void markNeedsPaint() { if (_needsPaint) return; _needsPaint = true; if (isRepaintBoundary) { if (owner != null) { owner._nodesNeedingPaint.add(this); owner.requestVisualUpdate(); } } else if (parent is RenderObject) { final RenderObject parent = this.parent; parent.markNeedsPaint(); } else { if (owner != null) owner.requestVisualUpdate(); } } ~~~ 函数`markNeedsPaint()`首先做的是把自己的标志位`_needsPaint`设置为`true`。然后会向上查找最近的一个`isRepaintBoundary`为`true`的祖先节点。直到找到这样的节点,才会把这个节点加入到`_nodesNeedingPaint`列表中,也就是说,并不是任意一个需要重绘的`RenderObject`就会被加入这个列表,而是往上找直到找到最近的一个`isRepaintBoundary`为`true`才会放入这个列表,换句话说,这个列表里只有`isRepaintBoundary`为`true`这种类型的节点。也就是说重绘的起点是从“重绘边界”开始的。 ### flushPaint 的。 ~~~ void flushPaint() { try { final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; // Sort the dirty nodes in reverse order (deepest first). for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { if (node._needsPaint && node.owner == this) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } } finally { ... } } ~~~ 之前`flushLayout()`里的排序不同,这里的排序是深度度深的节点在前。在循环体里,会判断当前节点的`_layer`属性是否处于`attached`的状态。如果`_layer.attached`为`true`的话调用`PaintingContext.repaintCompositedChild(node);`去做绘制,否则的话调用`node._skippedPaintingOnLayer()`将自身以及到上层绘制边界之间的节点的`_needsPaint`全部置为`true`。这样在下次`_layer.attached`变为`true`的时候会直接绘制。 从上述代码也可以看出,重绘边界相当于把Flutter的绘制做了分块处理,重绘的从上层重绘边界开始,到下层重绘边界为止,在此之间的`RenderObject`都需要重绘,而边界之外的就可能不需要重绘,这也是一个性能上的考虑,尽量避免不必要的绘制。所以如何合理安排`RepaintBoundary`是我们在做Flutter app的性能优化时候需要考虑的一个方向。 这里的`_layer`属性就是我们之前说的图层,这个属性只有绘制边界的`RenderObject`才会有值。一般的`RenderObject`这个属性是`null`。 。 ### repaintCompositedChild ~~~ static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false, PaintingContext childContext, }) { if (child._layer == null) { child._layer = OffsetLayer(); } else { child._layer.removeAllChildren(); } childContext ??= PaintingContext(child._layer, child.paintBounds); child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); } ~~~ 先检查`RenderObject`的图层属性,为空则新建一个`OffsetLayer`实例。如果图层已经存在的话就把孩子清空。 ### PaintingContext 如果没有`PaintingContext`的话会新建一个,然后让开始绘制。我们先来看一下`PaintingContext`这个类: ~~~ class PaintingContext extends ClipContext { @protected PaintingContext(this._containerLayer, this.estimatedBounds) final ContainerLayer _containerLayer; final Rect estimatedBounds; PictureLayer _currentLayer; ui.PictureRecorder _recorder; Canvas _canvas; @override Canvas get canvas { if (_canvas == null) _startRecording(); return _canvas; } void _startRecording() { _currentLayer = PictureLayer(estimatedBounds); _recorder = ui.PictureRecorder(); _canvas = Canvas(_recorder); _containerLayer.append(_currentLayer); } void stopRecordingIfNeeded() { if (!_isRecording) return; _currentLayer.picture = _recorder.endRecording(); _currentLayer = null; _recorder = null; _canvas = null; } ~~~ 类`PaintingContext`字面意思是绘制上下文,其属性`_containerLayer`是容器图层,来自构造时的入参。也就是说`PaintingContext`是和容器图层关联的。接下来还有`PictureLayer`类型的`_currentLayer`属性, `ui.PictureRecorder`类型的`_recorder`属性和我们熟悉的`Canvas`类型的属性`_canvas`。函数`_startRecording()` 实例化了这几个属性。`_recorder`用来录制绘制命令,`_canvas`绑定一个录制器。最后,`_currentLayer`会作为子节点加入到`_containerLayer`中。有开始那么就会有结束,`stopRecordingIfNeeded()`用来结束当前绘制的录制。结束时会把绘制完毕的`Picture`赋值给当前的`PictureLayer.picture`。 ### paintWithContext `RenderObject._paintWithContext()`开始绘制了,这个函数会直接调用到我们熟悉的`RenderObject.paint(context, offset)`,`paint()`由`RenderObject`子类自己实现 ### RenderRepaintBoundary的paint 从之前的源码分析我们知道绘制起点都是“绘制边界”。这里我们就拿我们熟悉的一个“绘制边界”,`RenderRepaintBoundary`,为例来走一下绘制流程,它的绘制函数的实现在`RenderProxyBoxMixin`类中: : ~~~ @override void paint(PaintingContext context, Offset offset) { if (child != null) context.paintChild(child, offset); } ~~~ 这个调用又回到了`PaintingContext`的`paintChild()`方法: ~~~ void paintChild(RenderObject child, Offset offset) { if (child.isRepaintBoundary) { stopRecordingIfNeeded(); _compositeChild(child, offset); } else { child._paintWithContext(this, offset); } } ~~~ 这里会检查子节点是不是绘制边界,如果不是的话,就是普通的绘制了,接着往下调用`_paintWithContext()`,继续往当前的`PictureLayer`上绘制。如果是的话就把当前的绘制先停掉。然后调用`_compositeChild(child, offset);` ~~~ void _compositeChild(RenderObject child, Offset offset) { if (child._needsPaint) { repaintCompositedChild(child, debugAlsoPaintedParent: true); } child._layer.offset = offset; appendLayer(child._layer); } ~~~ 如果这个子绘制边界被标记为需要重绘的话,那么就调用`repaintCompositedChild()`来重新生成图层然后重绘。如果这个子绘制边界**没有**被标记为需要重绘的话,就跳过了重新生成图层和重绘。最后只需要把子图层加入到当前容器图层中就行了。 ### RenderObject的paint 这里就拿Flutter app出错控件的绘制做个例子: ~~~ void paint(PaintingContext context, Offset offset) { try { context.canvas.drawRect(offset & size, Paint() .. color = backgroundColor); double width; if (_paragraph != null) { // See the comment in the RenderErrorBox constructor. This is not the // code you want to be copying and pasting. :-) if (parent is RenderBox) { final RenderBox parentBox = parent; width = parentBox.size.width; } else { width = size.width; } _paragraph.layout(ui.ParagraphConstraints(width: width)); context.canvas.drawParagraph(_paragraph, offset); } } catch (e) { // Intentionally left empty. } } ~~~ 这看起来就像个正常的绘制了,我们会用来自`PaintingContext`的画布`canvas`来绘制矩形,绘制文本等等。从前面的分析也可以看出,这里的绘制都是在一个`PictureLayer`的图层上所做的。 ## renderView.compositeFrame() 这里的`renderView`就是我们之前说的render tree的根节点。这个函数调用主要是把整个layer tree生成`scene`送到engine去显示。 ~~~ void compositeFrame() { try { final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer.buildScene(builder); if (automaticSystemUiAdjustment) _updateSystemChrome(); _window.render(scene); scene.dispose(); } finally { Timeline.finishSync(); } } ~~~ `ui.SceneBuilder()`最终调用Native方法`SceneBuilder_constructor`。也就是说`ui.SceneBuilder`实例是由engine创建的。接下来就是调用`layer.buildScene(builder)`方法,这个方法会返回一个`ui.Scene`实例。由于方法`compositeFrame()`的调用者是`renderView`。所以这里这个`layer`是来自`renderView`的属性,我们前面说过只有绘制边界节点才有`layer`。所以可见render tree的根节点`renderView`也是一个绘制边界。那么这个`layer`是从哪里来的呢?在文章《Flutter框架分析(二)-- 初始化》我们讲过,框架初始化的过程中`renderView`会调度开天辟地的第一帧: ~~~ void scheduleInitialFrame() { scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); owner.requestVisualUpdate(); } Layer _updateMatricesAndCreateNewRootLayer() { _rootTransform = configuration.toMatrix(); final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform); rootLayer.attach(this); return rootLayer; } void scheduleInitialPaint(ContainerLayer rootLayer) { _layer = rootLayer; owner._nodesNeedingPaint.add(this); } 复制代码 ~~~ 在方法`_updateMatricesAndCreateNewRootLayer()`中,我们看到这里实例化了一个`TransformLayer`。`TransformLayer`继承自`OffsetLayer`。构造时需要传入`Matrix4`类型的参数`transform`。这个`Matrix4`其实和我们在Android中见到的`Matrix`是一回事。代表着矩阵变换。这里的`transform`来自我们之前讲过的`ViewConfiguration`,它就是把设备像素比例转化成了矩阵的形式。最终这个`layer`关联上了`renderView`。所以这里这个`TransformLayer`其实也是layer tree的根节点了。 回到我们的绘制流程。`layer.buildScene(builder);`这个调用我们自然是去 `TransformLayer`里找了,但这个方法是在其父类`OffsetLayer`内,从这个调用开始就都是对图层进行操作,最终把layer tree转换为场景`scene`: ~~~ ui.Scene buildScene(ui.SceneBuilder builder) { List<PictureLayer> temporaryLayers; updateSubtreeNeedsAddToScene(); addToScene(builder); final ui.Scene scene = builder.build(); return scene; } 复制代码 ~~~ 函数调用`updateSubtreeNeedsAddToScene();`会遍历layer tree来设置`_subtreeNeedsAddToScene`标志位,如果有任意子图层的添加、删除操作,则该子图层及其祖先图层都会被置上`_subtreeNeedsAddToScene`标志位。然后会调用addToScene(builder); ~~~ @override @override ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) { _lastEffectiveTransform = transform; final Offset totalOffset = offset + layerOffset; if (totalOffset != Offset.zero) { _lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0) ..multiply(_lastEffectiveTransform); } builder.pushTransform(_lastEffectiveTransform.storage); addChildrenToScene(builder); builder.pop(); return null; // this does not return an engine layer yet. } 复制代码 ~~~ `builder.pushTransform`会调用到engine层。相当于告诉engine这里我要加一个变换图层。然后调用`ddChildrenToScene(builder)`将子图层加入场景中,完了还要把之前压栈的变换图层出栈。 ~~~ void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) { Layer child = firstChild; while (child != null) { if (childOffset == Offset.zero) { child._addToSceneWithRetainedRendering(builder); } else { child.addToScene(builder, childOffset); } child = child.nextSibling; } } 复制代码 ~~~ 这就是遍历添加子图层的调用。主要还是逐层向下的调用`addToScene()`。这个方法不同的图层会有不同的实现,对于容器类图层而言,主要就是做三件事:1.添加自己图层的效果然后入栈,2.添加子图层,3. 出栈。 在所有图层都处理完成之后。回到`renderView.compositeFrame()`,可见最后会把处理完得到的场景通过`_window.render(scene);`调用送入engine去显示了。