Flutter Framework层UI绘制 buildScope() 方法(二)
本文记录自己学习 Flutter 绘制所见源码,并不是全面详细的解析 Blog。
Flutter 源码环境:1.22.1
上篇认识到 drawFrame() 方法是 Flutter 绘制的入口,来看看这个方法做了些什么?
flutter/lib/src/widgets/binding.dart
//此方法由[handleDrawFrame]调用,当需要布置和绘制框架时,引擎会自动调用该方法。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 performRebuildperformRebuild();}
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 的时候被调起。@overridevoid 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
@protectedElement 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.2child.update(newWidget);newChild = child;} else {deactivateChild(child);// 调用 1.3.3newChild = 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
@overridevoid update(StatefulWidget newWidget) {super.update(newWidget);assert(widget == newWidget);final StatefulWidget oldWidget = _state._widget;_dirty = true;_state._widget = widget as StatefulWidget;try {_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);//调用didUpdateWidgetfinal dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;} finally {...}//调用rebuild()rebuild();}
该方法首先会调用 didUpdateWidget,接着调用 rebuild()方法,然后不停的循环重复,我们继续看 inflateWidget 方法。
1.3.3 inflateWidget
flutter/lib/src/widgets/framework.dart
Element inflateWidget(Widget newWidget, dynamic newSlot) {assert(newWidget != null);final Key key = newWidget.key;if (key is GlobalKey) {//调用_retakeInactiveElementfinal 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
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
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 遍历完。
