Android|Flutter的绘制流程简述

相对于React NativeWeex等跨平台框架,Flutter拥有自己的UI绘制体系,避免了React NativeWeex等跨平台框架与Native系统的桥接,从而更好的提升了性能。
Flutter中,UI都是一帧一帧的绘制,但这绘制的背后都会经过如下阶段。

  1. 动画与微任务阶段,主要是处理动画及执行一系列微任务。
  2. 构建阶段(build),找出标记为“脏”的节点与布局边界之间的所有节点,并做相应的更新。
  3. 布局阶段,计算Widget的大小及位置的确定。
  4. compositingBits阶段,重绘之前的预处理操作,检查RenderObject是否需要重绘。
  5. 绘制阶段,根据Widget大小及位置来绘制UI。
  6. compositing阶段,将UI数据发送给GPU处理。
  7. semantics阶段,与平台的辅助功能相关。
  8. finalization阶段,主要是从Element树中移除无用的Element对象及处理绘制结束回调。
下面就来分析上述的各个阶段
1、动画与微任务阶段
【Android|Flutter的绘制流程简述】该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,很多时候都会添加一个回调函数来进行状态获取或数据更新,如通过addListeneraddStatusListener等函数来添加,而这些回调函数就会在本阶段来执行。具体是在SchedulerBinding中的handleBeginFrame函数中实现。
void handleBeginFrame(Duration rawTimeStamp) { ... try { // TRANSIENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.transientCallbacks; //切换为transientCallbacks阶段 final Map callbacks = _transientCallbacks; //清空已注册的回调函数 _transientCallbacks = {}; //遍历所有注册的回调方法 callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { if (!_removedIds.contains(id)) //执行已经注册的回调函数 _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack); }); _removedIds.clear(); } finally { //切换为midFrameMicrotasks阶段 _schedulerPhase = SchedulerPhase.midFrameMicrotasks; } }

_invokeFrameCallback就会调用在使用动画时注册的回调函数,这里仅执行一次。如果我们在运行时调用_invokeFrameCallback函数的代码注释调,那么就无法获取动画的状态,从而影响动画的正确执行。
当回调函数执行完毕后,就会进入微任务阶段,在该阶段会执行一系列微任务,由于这涉及到Flutter的异步任务体系,因此这里就不再叙述。
2、build阶段
在上一阶段执行完毕后,就进入build阶段,在该阶段主要是重新构建标记为“脏”的Widget节点及将需要更新的RenderObject对象标记为“脏”。
handleBeginFrame函数执行完毕后,就会执行handleDrawFrame函数,该函数在SchedulerBinding对象初始化时会与Window相关联,所以除第一次需要主动调用外,其他时候皆是通过Window来调用该函数。
void handleDrawFrame() { assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks); Timeline.finishSync(); // end the "Animate" phase try { //持久帧回调,该回调会一直存在,不会移除 _schedulerPhase = SchedulerPhase.persistentCallbacks; for (FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); //当前帧绘制完成回调 _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List localPostFrameCallbacks = List.from(_postFrameCallbacks); _postFrameCallbacks.clear(); //当执行这里时,代表当前帧已经绘制完毕 for (FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); } finally { //进入空闲状态 _schedulerPhase = SchedulerPhase.idle; Timeline.finishSync(); // end the Frame profile(() { _profileFrameStopwatch.stop(); _profileFramePostEvent(); }); _currentFrameTimeStamp = null; } }

这里重点关注持久帧回调,该回调也是UI绘制的关键函数,是在RendererBinding对象初始化时注册的。
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { @override void initInstances() { super.initInstances(); ... //注册持久帧回调 addPersistentFrameCallback(_handlePersistentFrameCallback); } void _handlePersistentFrameCallback(Duration timeStamp) { //绘制帧 drawFrame(); } //绘制帧 void drawFrame() { //对Widget进行测量、布局 pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); //对Widget进行绘制 pipelineOwner.flushPaint(); //发送数据给GPU renderView.compositeFrame(); // this sends the bits to the GPU pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. } }

根据函数名可以发现并没有发现关于构建Widget的相关函数,那么在何时构建尼?通过查看源码可以发现,在WidgetsBinding中重写了drawFrame函数。在该函数中会创建新的Widget对象替换旧的Widget对象并将不需要的Element节点从树中移除。
@override void drawFrame() { ... try { //如果根结点存在,就重新构建Widget if (renderViewElement != null) buildOwner.buildScope(renderViewElement); //调用RendererBinding中的drawFrame函数 super.drawFrame(); //移除不再使用的Element节点 buildOwner.finalizeTree(); } finally {...} ... }

2.1、重新build Widget对象 Widget对象的创建是在buildScope()函数中实现的,这是一个非常重要的函数,具体实现如下。
void buildScope(Element context, [ VoidCallback callback ]) { if (callback == null && _dirtyElements.isEmpty) return; try { //“脏”节点列表需要重新排序 _scheduledFlushDirtyElements = true; ... //将标记为“脏”的Element节点根据深度进行排序 _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; //标记为“脏”的Element节点数量 int dirtyCount = _dirtyElements.length; int index = 0; //遍历“脏”节点 while (index < dirtyCount) { try { //重新构建Widget,及是否复用当前Element _dirtyElements[index].rebuild(); } catch (e, stack) { ... } index += 1; //当_dirtyElements集合中的“脏”节点还未处理完毕时,又添加了新的“脏”节点 if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) { //根据“脏”节点的深度进行排序 _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; dirtyCount = _dirtyElements.length; //如果当前节点的深度比新加入的“脏”节点深度要深,则需要将处理坐标指向新加入的“脏”节点 while (index > 0 && _dirtyElements[index - 1].dirty) { index -= 1; } } } } finally { //清除_dirtyElements中所有节点的“脏”状态 for (Element element in _dirtyElements) { element._inDirtyList = false; } _dirtyElements.clear(); _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; Timeline.finishSync(); } }

_dirtyElements是一个集合,存储了所有标记为“脏”的节点。在对其中的“脏”节点进行处理时,需要首先对集合中的“脏”节点进行排序,其排序规则如下。
  • 如果“脏”节点的深度不同,则按照深度进行升序排序
  • 如果“脏”节点的深度相同,则会将“脏”节点放在集合的右侧,“干净”节点则在在集合的左侧。
在排序完成后,就要遍历该集合,对其中的“脏”节点进行处理。在这里调用的是rebuild函数,通过该函数,会重新创建“脏”节点下的所有Widget对象,并根据新的Widget对象来判断是否需要重用Element对象。一般只要不是增删WidgetElement对象都会被重用,从而也就会重用RenderObject对象。由于Widget是一个非常轻量级的数据结构,所以在UI更新时做到了把性能损耗降到最低。
这里要注意一点的是,如果_dirtyElements中的“脏”节点还未处理完毕,就又新增了“脏”节点,那么这时候就会重新排序,保证_dirtyElements集合的左侧永远是“干净”节点,右侧永远是“脏”节点。
由于rebuild函数比较重要,这里就重点介绍一下该函数,在rebuild函数中会调用performRebuild函数,该函数是一个抽象函数,在其子类实现,而标记为“脏”的Element都是StatefulElement。所以就来StatefulElement或者其父类中查找performRebuild函数。
abstract class ComponentElement extends Element { ... @override void performRebuild() { Widget built; try { //重新创建新的`Widget`对象 built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { //当构建Widget对象出错时展示的默认页面,可以修改该页面来使异常界面更友好的显示 built = ErrorWidget.builder(_debugReportException('building $this', e, stack)); } finally { //清除“脏”标记 _dirty = false; } try { //更新子Element对应的Widget对象 _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { //当构建Widget对象出错时展示的默认页面 built = ErrorWidget.builder(_debugReportException('building $this', e, stack)); _child = updateChild(null, built, slot); } } }

performRebuild函数做的事很简单,就是创建新的Widget对象来替换旧的对象。上面的build函数调用的就是State类中的build函数,然后再调用ElementupdateChild函数,该函数在Flutter之Widget层级介绍中进行了简单的介绍,就是更新Element对应的Widget对象。而在updateChild函数中又会调用子Elementupdate函数,从而调用子ElementperformRebuild,然后在调用子ElementupdateChildupdate函数。以此类推,从而更新其所有子ElementWidget对象。
Android|Flutter的绘制流程简述
文章图片

最后就是调用叶子节点的updateRenderObject函数来更新RenderObject。在更新RenderObject对象时,会根据情况来对需要重新布局及重新绘制的RenderObject对象进行标记。然后等待下一次的Vsync信号时来重新布局及绘制UI。
2.2、标记RenderObject 对于RenderObject对象,可以通过markNeedsLayoutmarkNeedsPaint来标记是否需要重新布局及重新绘制。但在当前阶段只会调用markNeedsLayout来标记需要重新布局的RenderObject对象,在下一阶段才会标记需要重新绘制的RenderObject,所以先来看markNeedsLayout函数。
void markNeedsLayout() { ... //判断布局边界是否是是当前RenderObject对象 if (_relayoutBoundary != this) { markParentNeedsLayout(); } else { _needsLayout = true; if (owner != null) { //标记当前RenderObject及其子RenderObject对象需要重新布局 //将当前`RenderObject`添加到集合中。 owner._nodesNeedingLayout.add(this); owner.requestVisualUpdate(); } } } @protected void markParentNeedsLayout() { _needsLayout = true; final RenderObject parent = this.parent; if (!_doingThisLayoutWithCallback) { //调用父类的markNeedsLayout函数 parent.markNeedsLayout(); } else { assert(parent._debugDoingThisLayout); } assert(parent == this.parent); }

markNeedsLayout函数的代码实现很简单,就是不断遍历父RenderObject对象,从而找到布局边界的RenderObject对象,并将该RenderObject对象添加到集合_nodesNeedingLayout中,然后在下一阶段就从该对象开始布局。
在这里有个“布局边界”的概念,在Flutter中,可以给任意节点设置布局边界,即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。
在重新构建build函数及标记RenderObject完成后,就进入下一阶段,开始布局。
3、layout阶段
在该阶段,会确定每个组件的大小及位置,相当于Android中的onMeasure+onLayout函数所实现的功能。如果是第一次调用该函数,该阶段就会遍历所有的组件,来确定其大小及位置;否则该阶段就会遍历布局边界内的所有组件,来确定其大小及位置。
当上一阶段中的buildOwner.buildScope(renderViewElement)函数执行完毕后,就会调用RendererBindingdrawFrame函数,该函数实现非常简洁。
//绘制帧 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. }

其中flushLayout就是进行组件的大小及位置确定,在该函数中会遍历集合_nodesNeedingLayout并调用集合中每个对象的_layoutWithoutResize函数。
void flushLayout() { try { while (_nodesNeedingLayout.isNotEmpty) { final List dirtyNodes = _nodesNeedingLayout; _nodesNeedingLayout = []; for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) { //调用RenderObject对象的_layoutWithoutResize函数 if (node._needsLayout && node.owner == this) node._layoutWithoutResize(); } } } finally {...} }

_layoutWithoutResize函数是私有的,所以不存在重写的问题。那么就直接来看该函数。
void _layoutWithoutResize() { try { performLayout(); markNeedsSemanticsUpdate(); } catch (e, stack) {...} _needsLayout = false; markNeedsPaint(); }

_layoutWithoutResize函数很简单,就直接调用了performLayout函数。
由于performLayout是一个抽象函数,需要在子类重写,但都会在该函数中调用layout函数,然后又在layout函数中调用performLayout函数。以此类推,从而确定更新UI部分的组件大小及位置,总体流程如下。
Android|Flutter的绘制流程简述
文章图片

当然,RenderObject对象的size也不是随便确定的,因为在调用RenderObjectlayout函数时,会传递一个继承自Constraints的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定自己的大小。
3.1、标记RenderObject 当大小及位置确定后,就又会对RenderObject进行一次标记,这次跟上一阶段的标记大同小异,但这次是标记可绘制的RenderObject对象,然后在后面对这些对象进行重新绘制。标记可绘制的RenderObject对象是通过markNeedsPaint函数来实现的,代码如下。
void markNeedsPaint() { if (_needsPaint) return; _needsPaint = true; if (isRepaintBoundary) { //标记需要重新绘制的RenderObject对象 //需要绘制当前图层 if (owner != null) { owner._nodesNeedingPaint.add(this); owner.requestVisualUpdate(); } } else if (parent is RenderObject) { //没有自己的图层,与父类共用同一图层 final RenderObject parent = this.parent; //遍历其父RenderObject对象 parent.markNeedsPaint(); } else { //当是RenderView时,需要自己创建新的图层 if (owner != null) owner.requestVisualUpdate(); } }

markNeedsPaint函数中涉及到了一个“重绘边界”的概念。在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响。当然重绘边界也可以在任何节点手动设置,但是一般不需要我们来实现,Flutter提供的控件默认会在需要设置的地方自动设置。
4、compositingBits阶段
在组件的大小及位置确定后,就会进入当前阶段。该阶段主要是做一件事,就是将RenderObject树上新增及删除的RenderObject对象标记为“脏”,方便在下一阶段对这些RenderObject对象进行重绘。具体代码实现是在flushCompositingBits函数中,该函数在Layout阶段后立即调用。
void flushCompositingBits() { ... //将RenderObject对象按照深度进行排序 _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth); for (RenderObject node in _nodesNeedingCompositingBitsUpdate) { if (node._needsCompositingBitsUpdate && node.owner == this) //将RenderObject对象及其子对象标记为“脏” node._updateCompositingBits(); } _nodesNeedingCompositingBitsUpdate.clear(); ... }

_nodesNeedingCompositingBitsUpdate是一个集合,只有RenderObject对象的_needsCompositing为true时,才会添加到该集合中。在RenderObject对象创建时,_needsCompositing的值会根据isRepaintBoundaryalwaysNeedsCompositing来共同判断。
RenderObject() { //isRepaintBoundary决定当前RenderObject是否与父RenderObject分开绘制,默认为false,其值在当前对象的生命周期内无法修改。也就是判断当前对象是否是绘制边界 //alwaysNeedsCompositing为true表示当前RenderObject会一直重绘,如视频播放,默认为false _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing; }

然后在向树中添加或者删除RenderObject对象时会调用adoptChilddropChild函数,而这两个函数都会调用markNeedsCompositingBitsUpdate函数,也就在markNeedsCompositingBitsUpdate函数内完成了将当前对象添加到集合中的操作。
//向树中添加当前节点 @override void adoptChild(RenderObject child) { setupParentData(child); markNeedsLayout(); //将当前对象的_needsCompositingBitsUpdate值标为true markNeedsCompositingBitsUpdate(); markNeedsSemanticsUpdate(); super.adoptChild(child); } //从树中移除当前节点 @override void dropChild(RenderObject child) { child._cleanRelayoutBoundary(); child.parentData.detach(); child.parentData = https://www.it610.com/article/null; super.dropChild(child); markNeedsLayout(); //将当前对象的_needsCompositingBitsUpdate值标为true markNeedsCompositingBitsUpdate(); markNeedsSemanticsUpdate(); } // 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; } } //将当前对象或者其父对象添加到_nodesNeedingCompositingBitsUpdate集合中 if (owner != null) owner._nodesNeedingCompositingBitsUpdate.add(this); }

这样就会在调用flushCompositingBits函数时,就会调用_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; }

5、绘制阶段
经过前面的布局及“脏”RenderObject对象的标记,现在就可以在图层上进行UI的绘制。通过调用flushPaint函数就可以重绘已经标记的“脏”RenderObject对象及其子对象。
void flushPaint() { try { final List dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = []; //根据节点深度进行排序 for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { if (node._needsPaint && node.owner == this) { //当前对象是否与layer进行关联 if (node._layer.attached) { //在图层上绘制UI PaintingContext.repaintCompositedChild(node); } else { //跳过UI绘制,但当前节点为“脏”的状态不会改变 node._skippedPaintingOnLayer(); } } } } finally {} }

flushPaint函数中,每次遍历“脏”RenderObject对象时,都会进行一次排序,避免重复绘制。然后在判断当前对象是否与Layer进行关联,如果没有关联,则无法进行绘制,但不会清除“脏”标记。下面来看repaintCompositedChild函数的实现。
static void repaintCompositedChild(RenderObject child, { bool _repaintCompositedChild( child, debugAlsoPaintedParent: debugAlsoPaintedParent, ); }static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false, PaintingContext childContext, }) { //拿到Layer对象 OffsetLayer childLayer = child._layer; if (childLayer == null) { //创建新的Layer对象 child._layer = childLayer = OffsetLayer(); } else { //移除Layer对象的后继节点 childLayer.removeAllChildren(); } //创建context对象 childContext ??= PaintingContext(child._layer, child.paintBounds); //调用paint函数开始绘制 child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); }

在该函数中主要是对Layer对象的处理,然后调用_paintWithContext函数,在_paintWithContext函数中就会调用paint这个函数,从而实现UI的绘制。至此,就完成了UI的绘制,下面再来看一个被忽略的对象——Layer
5.1、Layer Layer是“图层”意思,在Flutter中是最容易被忽略但又无比重要的一个类。它非常贴近底层,可以很容易的看到调用Native方法。
Layer跟其他三棵树一样,也是一棵树,有“脏”状态的标记、更新等操作。不同的是,Layer是一个双链表结构,在每个Layer对象中都会指向其前置节点与后置节点(叶子Layer的后置节点为null)。
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { //返回父节点 @override ContainerLayer get parent => super.parent; //当前节点状态,为true表示当前节点是“脏”数据。需要重绘 bool _needsAddToScene = true; //将当前节点标记为“脏” @protected @visibleForTesting void markNeedsAddToScene() { // Already marked. Short-circuit. if (_needsAddToScene) { return; } _needsAddToScene = true; }@protected bool get alwaysNeedsAddToScene => false; //这个是一个非常重要的东西,主要用于节点数据的缓存。存储当前节点的渲染数据,如果当前节点不需要更新,就直接拿存储的数据使用。 @protected ui.EngineLayer get engineLayer => _engineLayer; //更改当前节点的数据 @protected set engineLayer(ui.EngineLayer value) { _engineLayer = value; if (parent != null && !parent.alwaysNeedsAddToScene) { //将父节点标记需要更新的状态 parent.markNeedsAddToScene(); } } } ui.EngineLayer _engineLayer; //更新当前节点状态,如果_needsAddToScene为true,则将当前节点标记为“脏” @protected @visibleForTesting void updateSubtreeNeedsAddToScene() { _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene; }//指向后置节点 Layer get nextSibling => _nextSibling; Layer _nextSibling; //指向前置节点 Layer get previousSibling => _previousSibling; Layer _previousSibling; //将子节点从Layer树中移除 @override void dropChild(AbstractNode child) { if (!alwaysNeedsAddToScene) { markNeedsAddToScene(); } super.dropChild(child); } //将当前节点添加到Layer树中 @override void adoptChild(AbstractNode child) { if (!alwaysNeedsAddToScene) { markNeedsAddToScene(); } super.adoptChild(child); }//将当前节点从Layer树中移除 @mustCallSuper void remove() { parent?._removeChild(this); }void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]); void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) { //使用当前节点的缓存的数据 if (!_needsAddToScene && _engineLayer != null) { builder.addRetained(_engineLayer); return; } addToScene(builder); //将当前节点标记为“干净”的 _needsAddToScene = false; } }

5.2、Layer节点的添加 previousSiblingnextSibling分别是Layer的前置节点与后置节点,当向Layer树中添加Layer节点时,也会将当前Layer设置为父节点的后置节点,父节点设置为当前节点的前置节点。这样,就形成了一颗树。
class ContainerLayer extends Layer { ... //将当前节点及其链表上的所有子节点都加入到Layer树中 @override void attach(Object owner) { super.attach(owner); Layer child = firstChild; while (child != null) { child.attach(owner); child = child.nextSibling; } }//将当前节点及其链表上的所有子节点都从Layer树中移除 @override void detach() { super.detach(); Layer child = firstChild; while (child != null) { child.detach(); child = child.nextSibling; } } //将child添加到链表中 void append(Layer child) { adoptChild(child); child._previousSibling = lastChild; if (lastChild != null) lastChild._nextSibling = child; _lastChild = child; _firstChild ??= child; } ... }

在上述的append函数中就将子节点添加到Layer树并加入到双链表中。在adoptChild函数中最终会调用attach函数,从而完成Layer树的添加。
5.3、Layer的状态更新 _needsAddToScene是对Layer状态的标记,如果为true,则表示当前Layer需要重写进行绘制,否则表示当前Layer是“干净”的,不需要重新绘制,只需要拿Layer上次的数据与其他Layer数据一起交给GPU处理即可。从而达到节省资源的目的。
class ContainerLayer extends Layer { ... //更新Layer节点的状态。 @override void updateSubtreeNeedsAddToScene() { super.updateSubtreeNeedsAddToScene(); Layer child = firstChild; while (child != null) { child.updateSubtreeNeedsAddToScene(); _needsAddToScene = _needsAddToScene || child._needsAddToScene; child = child.nextSibling; } } ... }

updateSubtreeNeedsAddToScene函数就是更新Layer的状态,可以发现,在更新当前Layer的状态时,也会更新其所有子Layer的状态。
关于Layer的更多内容可以去阅读Flutter Framework 源码解析( 2 )—— 图层详解这篇文章。
6、其他阶段
6.1、compositing阶段 该阶段主要是将更新后的数据传递给GPU。这时候调用的是compositeFrame函数,该函数很简单,就是调用了一个Native函数。
void compositeFrame() { Timeline.startSync('Compositing', arguments: timelineWhitelistArguments); try { final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer.buildScene(builder); if (automaticSystemUiAdjustment) _updateSystemChrome(); //更新后数据交给GPU处理 _window.render(scene); scene.dispose(); } finally { Timeline.finishSync(); } }

6.2、semantics阶段 在向GPU发送数据后,Flutter还会调用flushSemantics函数。该函数与系统的辅助功能相关,一般情况下是不做任何处理。
6.3、finalization阶段 在该阶段,主要是将Element对象从树中移除及处理添加在_postFrameCallbacks集合中的回调函数。由于该回调函数是在绘制结束时调用,所以在该回调函数中,context已经创建成功。
7、总结
可以发现,Flutter的UI绘制还是蛮复杂的,涉及到的东西也比较多,如动画的处理、辅助功能、异步任务等。但整体上还是通过WidgetElementRenderObject这三棵树来操作layer树实现的UI的绘制。
熟悉了这四棵树,也就会对Flutter的UI绘制有一个清晰的认识。

    推荐阅读