Flutter Framework层UI绘制 buildScope() 方法(二)

Flutter学习簿

共 9222字,需浏览 19分钟

 ·

2021-05-17 02:14

本文记录自己学习 Flutter 绘制所见源码,并不是全面详细的解析 Blog。

Flutter 源码环境:1.22.1

上篇认识到 drawFrame()  方法是 Flutter 绘制的入口,来看看这个方法做了些什么?

flutter/lib/src/widgets/binding.dart

//此方法由[handleDrawFrame]调用,当需要布置和绘制框架时,引擎会自动调用该方法。@override  void drawFrame() {    ...    try {      if (renderViewElement != null)      // 1. 调用 buildScope()        buildOwner.buildScope(renderViewElement);        //2.调用super.drawFrame();      super.drawFrame();      // 3. 调用finalizeTree()      buildOwner.finalizeTree();    } finally {      ...    }    ...  }

可知 drawFrame()方法分别调用了三个方法。讲道理只要搞清楚这三个方法是干什么的,那也就搞清楚了 Flutter 是怎么去绘制,更新 UI 的,先来看第一个 buildScope(renderViewElement)方法。

1.buildScope()

flutter/lib/src/widgets/framework.dart

  void buildScope(Element context, [ VoidCallback callback ]) {    if (callback == null && _dirtyElements.isEmpty)      return;      ...     Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);    try {      _scheduledFlushDirtyElements = true;      if (callback != null) {
_dirtyElementsNeedsResorting = false; try { callback(); } finally { ... } } //调用1.1 _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; int dirtyCount = _dirtyElements.length; int index = 0; while (index < dirtyCount) { ... try { _dirtyElements[index].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) {
index -= 1; } } } ... } finally { for (final Element element in _dirtyElements) { assert(element._inDirtyList); element._inDirtyList = false; } _dirtyElements.clear(); _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; Timeline.finishSync(); ... }
}

首先把 _scheduledFlushDirtyElements 属性标记为 true,表示正在重新构建新的控件树,从而避免 setState()方法调用的时候重复绘制。然后对_dirtyElements 链表依据 Element._sort 的规则做了一次排序:

1.1 Element._sort

flutter/lib/src/widgets/framework.dart

   //排序  static int _sort(Element a, Element b) {    if (a.depth < b.depth)      return -1;    if (b.depth < a.depth)      return 1;    if (b.dirty && !a.dirty)      return -1;    if (a.dirty && !b.dirty)      return 1;    return 0;  }

经此排序,depth 小的 Element 会排最前,depth 大的排最后;也就是说父 Element 会比子 Element 更早被 rebuild。这样可以防止 Element 重复 rebuild。

想象一下,就像一个树从树根输送养料,先从跟到径,再到达各个树梢,然后到达各个叶子。

然后接着会不断循环调用 _dirtyElements[index].rebuild()方法,去重构 Element,接下来我们还发现 如果_dirtyElementsNeedsResorting(属性字面意思就是 脏节点是否需要重新排序)为真。_dirtyElements 会再次排序。

这种情况就发生在 当在 rebuild 过程中有可能会通过 setState()方法加入新的 Dirty Element,所以每次 rebuild 的时候都会重新检查_dirtyElements 是否有增加或者检查_dirtyElementsNeedsResorting 标记位,接着从新排序一遍,然后移动对应的下标 index,以确保每一个 Element 都会被 rebuild。

接着看 rebuild()方法。

1.2 rebuild()

flutter/lib/src/widgets/framework.dart

void rebuild() {    assert(_debugLifecycleState != _ElementLifecycle.initial);    if (!_active || !_dirty)      return;     ...    // 调用 1.3 performRebuild    performRebuild();  }

1.3 performRebuild()

到这里可能会找不到谁调用了 performRebuild(),回头看一下,调用 performRebuild 的是  Element 类型对象的持有者,那么就从最常见的 StatefulElement 类找,然后发现 StatefulElement 继承自 ComponentElement,然后在去 ComponentElement 中找。反正我就是这样找到的。

flutter/lib/src/widgets/framework.dart


/// Called automatically during [mount] to generate the first build, and by /// [rebuild] when the element needs updating. // 该方法会在 mount,和rebuild 的时候被调起。 @override void performRebuild() { ... Widget built; try { built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { _debugDoingBuild = false; built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); }, ), ); } finally { // We delay marking the element as clean until after calling build() so // that attempts to markNeedsBuild() during build() will be ignored. _dirty = false;
} try { // 调用 1.3.1 _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); }, ), ); _child = updateChild(null, built, slot); }
}

该方法会 build 出子控件树,然后调起 updateChild() 方法。

1.3.1 updateChild()

flutter/lib/src/widgets/framework.dart

  @protected  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {    if (newWidget == null) {      //1. 把节点加入_inactiveElements链表,准备之后的删除或者重用      if (child != null)        deactivateChild(child);      return null;    }    Element newChild;    if (child != null) {        // 2.      bool hasSameSuperclass = true;       ...      if (hasSameSuperclass && child.widget == newWidget) {        if (child.slot != newSlot)          updateSlotForChild(child, newSlot);        newChild = child;      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {        if (child.slot != newSlot)          updateSlotForChild(child, newSlot);
//调用 1.3.2 child.update(newWidget); newChild = child; } else { deactivateChild(child);
// 调用 1.3.3 newChild = inflateWidget(newWidget, newSlot); } } else { //3. newChild = inflateWidget(newWidget, newSlot); }
return newChild; }
机器翻译
  • 此方法是小部件系统的核心。每当我们要基于更新的配置添加,更新或删除子代时,都会调用它。

  • 如果 child为空,而 newWidget不为空,则我们有一个新的子级,我们需要为其创建一个[Element],并用 newWidget配置。

  • 如果 newWidget为 null,而 child不为 null,则我们将其删除,因为它不再具有其配置信息。

  • 如果两者都不为 null,则需要将“子”的配置更新为“ newWidget”给定的新配置。如果可以将“ newWidget”提供给现有的子项(由[Widget.canUpdate]确定),则将其给定。否则,需要处置旧的子项,并为新配置创建一个新的子项。

  • 如果两者都为 null,则我们没有孩子,也不会有孩子,因此我们什么也不做。

方法核心工作
  • 如果 newWidget 为 null 但是 child 不为 null,会删除原来的控件,就会调起 deactivateChild 方法,会把当前的 Element 加入到 _inactiveElements 链表中(最后可能会被清除也可能会被重用)

  • 如果 newWidget 和 child 都不为 null,就更新原来的控件,先调起 Widget.canUpdate 方法或者判断两对象是否是一个,判断是否能够更新(一般都是根据 Widget 运行时类型是否相同来判断),如果相同调起 update 方法,继续更新的逻辑,如果不一样,就要 deactivate 原来的控件,并且创建新的控件。

  • 如果 child 为 null 而 Widegt 不为 null,也就是要创建新的控件。

StatefulElement.update()

flutter/lib/src/widgets/framework.dart

  @override  void update(StatefulWidget newWidget) {    super.update(newWidget);    assert(widget == newWidget);    final StatefulWidget oldWidget = _state._widget;
_dirty = true; _state._widget = widget as StatefulWidget; try { _debugSetAllowIgnoredCallsToMarkNeedsBuild(true); //调用didUpdateWidget final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic; } finally { ... } //调用rebuild() rebuild(); }

该方法首先会调用 didUpdateWidget,接着调用 rebuild()方法,然后不停的循环重复,我们继续看 inflateWidget 方法。

1.3.3 inflateWidget

flutter/lib/src/widgets/framework.dart


@protected Element inflateWidget(Widget newWidget, dynamic newSlot) { assert(newWidget != null); final Key key = newWidget.key; if (key is GlobalKey) { //调用_retakeInactiveElement final Element newChild = _retakeInactiveElement(key, newWidget); if (newChild != null) {
newChild._activateWithParent(this, newSlot); final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild; } } // createElement 这个方法是不是超级眼熟 final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild; }

这里判断 key 是否为 GlobalKey,如果是,会调起_retakeInactiveElement 方法,目的是从 _inactiveElements 链表上重用控件,并把控件从 BuilderOwner._inactiveElements 列表上移除,防止它被 unmount,接着就是从新跑一次 updateChild 流程;如果不是就在新的子控件上创建新的 Element,然后挂载 mount 上去。

最终 会调到 RenderObjectElement.update 方法。

1.3.4 RenderObjectElement.update

flutter/lib/src/widgets/framework.dart

  @override  void update(covariant RenderObjectWidget newWidget) {    super.update(newWidget);

// 调用 子类重写的 updateRenderObject方法。 widget.updateRenderObject(this, renderObject);
//调用updateRenderObject后将_dirty 设为false _dirty = false; }

来找一个重写了 updateRenderObject 方法的控件类,我找到了 Stack 这个控件类。

1.3.5

flutter/lib/src/widgets/basic.dart

  @override  void updateRenderObject(BuildContext context, RenderStack renderObject) {    assert(_debugCheckHasDirectionality(context));    //刷新配置    renderObject      ..alignment = alignment      ..textDirection = textDirection ?? Directionality.of(context)      ..fit = fit      ..clipBehavior = overflow == Overflow.visible ? Clip.none : clipBehavior;  }

updateRenderObject renderObject 对象的属性值进行更新。

然后不断的循环,直至_dirtyElements 链表元素中的 element 遍历完。

至此 drawFrame()中第一步 buildScope 方法中所做的工作内容基本完了,其所作的只是对绘制控件的参数数值进行更新,该方法中并没有真正的绘制。如果我看漏的,欢迎指正。

后记

以上内容涉及到两个很重要的类,一个是 BuilderOwner,一个是 RenderObject,都挺重要的,往后再看看吧。


浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报