likes
comments
collection
share

Flutter渲染机制分析(三) —— Build构建

作者站长头像
站长
· 阅读数 20

Flutter渲染机制分析(一) —— setState详解

Flutter渲染机制分析(二) —— Element

前情回顾

文章开头我们先来回顾一个方法:

mixin RenderBinding {
    @protected
    void drawFrame() {
      assert(renderView != null);
      pipelineOwner.flushLayout();
      pipelineOwner.flushCompositingBits();
      pipelineOwner.flushPaint();
      if (sendFramesToEngine) {
        renderView.compositeFrame(); // this sends the bits to the GPU
        pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
        _firstFrameSent = true;
      }
    }
}

上篇文章我们讲到调度一帧后,方法最终走到了drawFrame。这里我们看到drawFrame方法是在mixinRdnerBinding中,通篇看下来我们似乎没有看到一个很重要的步骤Build构建,也就是说很有可能有子类对drawFrame进行了覆写,下面我们就来看看能不能在子类中找到它呢。

WidgetsBinding

drawFrame

runApp中我们知道,WidgetsBinding混入了RenderBinding,我们来找找看,WidgetsBinding到底有没有自己实现drawFrame方法呢。

mixin WidgetsBinding {
  @override
  void drawFrame() { 
    try {
      if (renderViewElement != null) {
        buildOwner!.buildScope(renderViewElement!);
    }
    super.drawFrame();
    buildOwner!.finalizeTree();
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
}

WidgetsBinding中我们找到了drawFrame,去掉断言和与首帧渲染有关的代码后,我们找到了buildScope,这里就是我们在寻找的Build构建。

小提示Tips

/// Phases that can be reached by [WidgetTester.pumpWidget] and
/// [TestWidgetsFlutterBinding.pump].
///
/// See [WidgetsBinding.drawFrame] for a more detailed description of some of
/// these phases.
enum EnginePhase {
  /// The build phase in the widgets library. See [BuildOwner.buildScope].
  build,

  /// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
  layout,

  /// The compositing bits update phase in the rendering library. See
  /// [PipelineOwner.flushCompositingBits].
  compositingBits,

  /// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
  paint,

  /// The compositing phase in the rendering library. See
  /// [RenderView.compositeFrame]. This is the phase in which data is sent to
  /// the GPU. If semantics are not enabled, then this is the last phase.
  composite,

  /// The semantics building phase in the rendering library. See
  /// [PipelineOwner.flushSemantics].
  flushSemantics,

  /// The final phase in the rendering library, wherein semantics information is
  /// sent to the embedder. See [SemanticsOwner.sendSemanticsUpdate].
  sendSemanticsUpdate,
}

flutter/packages/flutter_test/lib/src/binding.dart文件的开头,我们可以找到这段代码及描述,里面详细描述了,各个阶段对应的具体执行方法.

BuildScope 做了什么

BuildOwner的创建和RenderViewElement前面都已经介绍过,这里就不再赘述。

buildOwner!.buildScope(renderViewElement!);

可以看到buildScope中传入了根节点renderViewElement

void buildScope(Element context, [ VoidCallback? callback ]) {
  if (callback == null && _dirtyElements.isEmpty) {
    return;
  }

  try {
    _scheduledFlushDirtyElements = true;
    _dirtyElements.sort(Element._sort);
    _dirtyElementsNeedsResorting = false;
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      final Element element = _dirtyElements[index];
      try {
        element.rebuild();
      } catch (e, stack) {
      }
      index += 1;
      if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        dirtyCount = _dirtyElements.length;
        while (index > 0 && _dirtyElements[index - 1].dirty) {
          // It is possible for previously dirty but inactive widgets to move right in the list.
          // We therefore have to move the index left in the list to account for this.
          // We don't know how many could have moved. However, we do know that the only possible
          // change to the list is that nodes that were previously to the left of the index have
          // now moved to be to the right of the right-most cleaned node, and we do know that
          // all the clean nodes were to the left of the index. So we move the index left
          // until just after the right-most clean node.
          index -= 1;
        }
      }
    }
  } finally {
    for (final Element element in _dirtyElements) {
      element._inDirtyList = false;
    }
    _dirtyElements.clear();
    _scheduledFlushDirtyElements = false;
    _dirtyElementsNeedsResorting = null;
  }
}

方法比较长,这里我们去掉断言和无关代码。通篇看下来该方法是围绕_dirtyElements“脏”Element来执行的,所以这里我们就要弄清楚_dirtyElements是什么。

关于dirtyElements是什么可以看上一篇文章BuildOwner做了什么,对dirtyElements做了介绍。

这里我们主要对BuildScope方法进行分析。

Element.sort “脏”Element排序

static int _sort(Element a, Element b) {
  final int diff = a.depth - b.depth;
  if (diff != 0) {
    return diff;
  
  final bool isBDirty = b.dirty;
  if (a.dirty != isBDirty) {
    return isBDirty ? -1 : 1;
  }
  return 0;
}

int get depth {
  return _depth;
}

对“”Elements列表进行排序后,接着对其进行遍历,依次取出其中的“脏”Element执行rebuild方法.

下面我们来看看rebuild方法具体逻辑.

Element.rebuild 重新构建

abstract class Element extends DiagnosticableTree implements BuildContext {
  void rebuild({bool force = false}) {
    if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
      return;
    }
    performRebuild();
  }
  
  void performRebuild() {
    _dirty = false;
  }
}

rebuild做的很简单,当前生命周期是active并且Element是”脏的“,执行了performReBuild,这个方法也很简单,将_dirty置为 false,主要实现逻辑在其子类RenderObjectElement中。

RenderObjectElement、RenderObjectWidget

abstract class RenderObjectElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  RenderObjectElement(RenderObjectWidget super.widget);

  @override
  void performRebuild() { // ignore: must_call_super, _performRebuild calls super.
    _performRebuild(); // calls widget.updateRenderObject()
  }

  @pragma('vm:prefer-inline')
  void _performRebuild() {
    (widget as RenderObjectWidget).updateRenderObject(this, renderObject);
    super.performRebuild(); // clears the "dirty" flag
  }
}

RenderObjectElement中执行的方法也很简单,实际调用了RenderObjectWidgetupdateRenderObject方法传入当前renderObject

小提示Tips:关于renderObject的创建可以在runApp的scheduleAttachRootWidget
中找到答案,在这里就不分析了,后面会写一篇Flutter三棵树的文章,在里面介绍.

下面我们来看看updateRenderObject又做了什么

abstract class RenderObjectWidget extends Widget {
 @protected
 void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
}

RenderObjectWidget是一个抽象类,里面所有的方法都是由其子类实现的。RenderObjectWidget子类有很多,这里我们就分析一种,它的子类SingleChildRenderObject

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const SingleChildRenderObjectWidget({ super.key, this.child });

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget? child;

  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

SingleChildRenderObectWidget同样是一个抽象类,并没有updateRenderObject的实现。我们同样要将目光转到其子类上。

查看其直接子类,我们发现了平时很常用的DecoratedBox装饰盒

class DecoratedBox extends SingleChildRenderObjectWidget {
  const DecoratedBox({
    super.key,
    required this.decoration,
    this.position = DecorationPosition.background,
    super.child,
  })

  @override
  void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
    renderObject
      ..decoration = decoration
      ..configuration = createLocalImageConfiguration(context)
      ..position = position;
  }
}

DecoratedBoxupdateRenderObject的实现非常简单,就是把新的参数值重新赋值给传入的renderObject

到这里一个Elementrebuild就结束了,我们把目光放回到BuildScope中,接着往看下面的代码


if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
  _dirtyElements.sort(Element._sort);
  _dirtyElementsNeedsResorting = false;
  dirtyCount = _dirtyElements.length;
  while (index > 0 && _dirtyElements[index - 1].dirty) {
    index -= 1;
  }
}

当前dirtyCount_dirtyElements数量不相等时,会重新获取dirtyCount,对dirtyElements,索引回退到第一个“”Element。也就是说当调用setState重新构建页面时,如果再次调用setState会重新从头开始构建。

buildScope方法的最后,清空“脏”列表dirtyElements

目光回归到WidgetsBindingdrawFrame中,当buildScope执行完毕后,会执行super.drawFrame方法,也就是我们开头提到的drawFrame,继续执行Layout布局、Paint绘制。

到这里Build的构建就查看完毕了,下面来总结一下

总结

  • RenderBinding中的drawFrame首先会执行WidgetsBinding中的drameFrame方法,接着执行自己的drawFrame
  • Build构建执行时机在WidgetBindingdrameFrame
  • performRebuild具体重建逻辑是在具体Widget中实现的

有一点需要注意,该系列文章的切入点是以setState为切入的进行分析的,所介绍的是重建的渲染机制分析。首次构建逻辑可以对照着本系列文章的思路,从runApp从头自己分析一遍。