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 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
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
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 遍历完。