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、动画与微任务阶段

该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,很多时候都会添加一个回调函数来进行状态获取或数据更新,如通过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对象。
Flutter的绘制流程简述_第1张图片
最后就是调用叶子节点的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部分的组件大小及位置,总体流程如下。
Flutter的绘制流程简述_第2张图片
当然,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 = 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绘制有一个清晰的认识。

你可能感兴趣的:(Android,Flutter,源码)