Flutter的绘制流程简述

相对于React NativeWeex等跨平台框架,Flutter拥有本身的UI绘制体系,避免了React NativeWeex等跨平台框架与Native系统的桥接,从而更好的提高了性能。html

Flutter中,UI都是一帧一帧的绘制,但这绘制的背后都会通过以下阶段。node

  1. 动画与微任务阶段,主要是处理动画及执行一系列微任务。
  2. 构建阶段(build),找出标记为“脏”的节点与布局边界之间的全部节点,并作相应的更新。
  3. 布局阶段,计算Widget的大小及位置的肯定。
  4. compositingBits阶段,重绘以前的预处理操做,检查RenderObject是否须要重绘。
  5. 绘制阶段,根据Widget大小及位置来绘制UI。
  6. compositing阶段,将UI数据发送给GPU处理。
  7. semantics阶段,与平台的辅助功能相关。
  8. finalization阶段,主要是从Element树中移除无用的Element对象及处理绘制结束回调。

下面就来分析上述的各个阶段git

一、动画与微任务阶段

该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,不少时候都会添加一个回调函数来进行状态获取或数据更新,如经过addListeneraddStatusListener等函数来添加,而这些回调函数就会在本阶段来执行。具体是在SchedulerBinding中的handleBeginFrame函数中实现。github

void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      //切换为transientCallbacks阶段
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      //清空已注册的回调函数
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      //遍历全部注册的回调方法
      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的异步任务体系,所以这里就再也不叙述。数据结构

二、build阶段

在上一阶段执行完毕后,就进入build阶段,在该阶段主要是从新构建标记为“脏”的Widget节点及将须要更新的RenderObject对象标记为“脏”。app

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<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.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节点从树中移除。ide

@override
  void drawFrame() {
    ...
    try {
      //若是根结点存在,就从新构建Widget
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      //调用RendererBinding中的drawFrame函数
      super.drawFrame();
      //移除再也不使用的Element节点
      buildOwner.finalizeTree();
    } finally {...}
    ...
  }
复制代码

2.一、从新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对象。

最后就是调用叶子节点的 updateRenderObject函数来更新 RenderObject。在更新 RenderObject对象时,会根据状况来对须要从新布局及从新绘制的 RenderObject对象进行标记。而后等待下一次的Vsync信号时来从新布局及绘制UI。

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完成后,就进入下一阶段,开始布局。

三、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<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        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部分的组件大小及位置,整体流程以下。

固然, RenderObject对象的size也不是随便肯定的,由于在调用 RenderObjectlayout函数时,会传递一个继承自 Constraints的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定本身的大小。

3.一、标记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提供的控件默认会在须要设置的地方自动设置。

四、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;
  }
复制代码

五、绘制阶段

通过前面的布局及“脏”RenderObject对象的标记,如今就能够在图层上进行UI的绘制。经过调用flushPaint函数就能够重绘已经标记的“脏”RenderObject对象及其子对象。

void flushPaint() {
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      //根据节点深度进行排序
      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.一、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.二、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.三、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.一、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.二、semantics阶段

在向GPU发送数据后,Flutter还会调用flushSemantics函数。该函数与系统的辅助功能相关,通常状况下是不作任何处理。

6.三、finalization阶段

在该阶段,主要是将Element对象从树中移除及处理添加在_postFrameCallbacks集合中的回调函数。因为该回调函数是在绘制结束时调用,因此在该回调函数中,context已经建立成功。

七、总结

能够发现,Flutter的UI绘制仍是蛮复杂的,涉及到的东西也比较多,如动画的处理、辅助功能、异步任务等。但总体上仍是经过WidgetElementRenderObject这三棵树来操做layer树实现的UI的绘制。 熟悉了这四棵树,也就会对Flutter的UI绘制有一个清晰的认识。

相关文章
相关标签/搜索