引言
在 Flutter 中,很多人都知道三棵树,最熟悉就是其中的 Widget 树了,这也是平常开发的过程中最多用到的东西,那么其他两棵树你知道是什么吗,了解他们的构建流程吗?
Widget 树
在开发过程中,与我们息息相关的就是 widget 了,几乎所有页面上显示的都是 widget ,Widget 是 Flutter 的核心,是用户界面的不可变描述。
事实上,widget 的功能就是描述一个 UI 元素的配置数据 ,也就是说 widget 并不是最终绘制到屏幕上的元素,它只是描述显示元素的一个配置而已。
在代码的运行过程中并没有明确的 widget 树的概念,这棵树是我我们在开发的过程中对 widget 嵌套的描述,因为确实长得像是一棵树
abstract class Widget { const Widget({ this.key }); final Key key; @protected Element createElement();//注释1 static bool canUpdate(Widget oldWidget, Widget newWidget) {//注释2 return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } }
Widget 本身是一个抽象类,接收一个 key,至于key的原理和使用可查看这篇文章,
注释1
createElement 是抽象方法,子类必须实现,该方法创建了一个 Element,所以每个 Element 都会对应一个 widget 对象。
注释2
判断 oldWidget 和 newWidget 是不是同一个 widget,如何 runtimeType 和 key 相同则认为是同一个 widget。
需要注意的是 widget 不能被修改,如果要修改只能重新创建了,因为 wdiget 并不参与渲染,它只是一个配置文件而已,只需要告诉渲染层自己的样式即可。
Element 树
Flutter 中真正显示到屏幕上的元素是 Element 类,也就是说 widget 只是描述 Element 的配置数据,并且 widget 可以对应多个 Element。这是因为同一个 widget 可以被添加到 Element 树的不同部分。而真正渲染的时候,每一个 Element 都会对应着一个 widget 对象。
所谓的 UI 树就是由一个个 Element 节点构成。组件的最终 Layout,渲染都是通过 RenderObject 来完成的,从创建到渲染的大体流程就是:根据 widget 生成 Element,然后在创建相应的 RenderObject 并关联到 Element.renderObject 属性上,最后在通过 RenderObject 来完成布局排列和绘制。
Element 表示一个 widget 树中特定位置的实例,大多数的 Element 只有惟一的 RenderObject,但是还有一些 Element 会有多个子节点,如继承自 RenderObjectElement 的一些类,比如 MultiChildRenderObjectObject。最终所有的 Element 的 RenderObject 构成一棵树,我们称之为 渲染树。
总结一下,我们可以认为 Flutter 的 UI 系统中包含了三棵树:Widget 树,Element 树,渲染树,他们的对应关系式 Element 树是根据 Widget 树生成,而渲染树有依赖于 Element 树,如图所示:
Element 类源代码
abstract class Element extends DiagnosticableTree implements BuildContext { Element(Widget widget) : assert(widget != null), _widget = widget; Element _parent; @override Widget get widget => _widget; Widget _widget; RenderObject get renderObject { ... } @mustCallSuper void mount(Element parent, dynamic newSlot) { ... } @mustCallSuper void activate() { ... } @mustCallSuper void deactivate() { ... } @mustCallSuper void unmount() { ... } }
Element 的生命周期
initial
初始状态
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
active
//RenderObjectElement 的 mount 方法 @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); //..... _renderObject = widget.createRenderObject(this); assert(_slot == newSlot); attachRenderObject(newSlot); _dirty = false; }
当 fragment 调用 element.mount 方法后,mount 方法中会首先调用 element 对应 widget 的 createRenderObject方法来创建与 element 对应的 RenderObject 对象。
然后调用 element.attachRenderObject 将 element.renderObject 添加到渲染树插槽的位置(这一步不是必须的,一般发生在 Element 树结构发生变化时才需要重新 attach。
插入到渲染后的 element 就处于 active 状态,处于 active状态后就可以显示在屏幕上了(可以隐藏)。
super.mount(parent,newslot)
_lifecycleState = _ElementLifecycle.active
当 widget 更新时,为了避免重新创建 element 会判断是否可以更新,会调用 updateChild 方法
@protected @pragma('vm:prefer-inline') Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { //当没有新 widget,并且有原来的widget,则移出原来的child,因为他不再有配置 if (newWidget == null) { if (child != null) deactivateChild(child); return null; } final Element newChild; //原来有 child if (child != null) { bool hasSameSuperclass = true; assert(() { final int oldElementClass = Element._debugConcreteSubtype(child); final int newWidgetClass = Widget._debugConcreteSubtype(newWidget); hasSameSuperclass = oldElementClass == newWidgetClass; return true; }()); // 如果父控件类型相同,子控件也相同,直接更新 if (hasSameSuperclass && child.widget == newWidget) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); newChild = child; } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) //父控件类型相同,并且可以更新 widget,则更新child if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); assert(child.widget == newWidget); assert(() { child.owner!._debugElementWasRebuilt(child); return true; }()); newChild = child; } else { //不能更新,需要先移出原有的 child,并创建新的 child 并添加 deactivateChild(child); assert(child._parent == null); newChild = inflateWidget(newWidget, newSlot); } } else { //没有 child,直接创建新的 child 并添加 newChild = inflateWidget(newWidget, newSlot); } return newChild; }
weidget.canUpdate ,主要判断 type 和 key 是否相同。如果我们需要强制更新,只需要修改key即可,官方不推荐修改 runtimetype
static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }
从“非活动”到“活动”生命周期状态的转换
在上面的 updateChild 方法中,最后如果调用了 inflateWidget() 方法后,就需要将状态从 inactive 转到 active 状态
@mustCallSuper void activate() { assert(_lifecycleState == _ElementLifecycle.inactive); assert(widget != null); assert(owner != null); assert(depth != null); final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies; _lifecycleState = _ElementLifecycle.active; // We unregistered our dependencies in deactivate, but never cleared the list. // Since we're going to be reused, let's clear our list now. _dependencies?.clear(); _hadUnsatisfiedDependencies = false; _updateInheritance(); if (_dirty) owner!.scheduleBuildFor(this); if (hadDependencies) didChangeDependencies(); }
inactive
从“活动”到“非活动”生命周期状态的转换
在上面的 updateChild 方法中我们可以看到新的 widget 为空并且存在旧的,就会调用deactiveChild 移除 child,然后调用 deactivate 方法将 _lifecycleState 设置为 inactive
@mustCallSuper void deactivate() { assert(_lifecycleState == _ElementLifecycle.active); assert(_widget != null); // Use the private property to avoid a CastError during hot reload. assert(depth != null); if (_dependencies != null && _dependencies!.isNotEmpty) { for (final InheritedElement dependency in _dependencies!) dependency._dependents.remove(this); } _inheritedWidgets = null; _lifecycleState = _ElementLifecycle.inactive; }
defunct
从“非活动”到“已失效”生命周期状态的转换
@mustCallSuper void unmount() { assert(_lifecycleState == _ElementLifecycle.inactive); assert(_widget != null); // Use the private property to avoid a CastError during hot reload. assert(depth != null); assert(owner != null); // Use the private property to avoid a CastError during hot reload. final Key? key = _widget!.key; if (key is GlobalKey) { owner!._unregisterGlobalKey(key, this); } // Release resources to reduce the severity of memory leaks caused by // defunct, but accidentally retained Elements. _widget = null; _dependencies = null; _lifecycleState = _ElementLifecycle.defunct; }
StatelessElement
Container 创建出来的是 StatelessElement,下面我们浅析一下他的调用过程,部分代码省略,只显示核心代码!
class StatelessElement extends ComponentElement { //通过 createElement 创建时传入的 widget StatelessElement(StatelessWidget widget) : super(widget); @override StatelessWidget get widget => super.widget as StatelessWidget; //这里调用的 build 就是我们自己实现的 build 方法 @override Widget build() => widget.build(this); } abstract class ComponentElement extends Element { /// Creates an element that uses the given widget as its configuration. ComponentElement(Widget widget) : super(widget); Element? _child; bool _debugDoingBuild = false; @override bool get debugDoingBuild => _debugDoingBuild; @override void mount(Element? parent, Object? newSlot) { _firstBuild(); } void _firstBuild() { rebuild(); } @override @pragma('vm:notify-debugger-on-exception') void performRebuild() { //..... } @protected Widget build(); } abstract class Element extends DiagnosticableTree implements BuildContext { // 构造方法, 接收一个widget参数 Element(Widget widget) : assert(widget != null), _widget = widget; @override Widget get widget => _widget; Widget _widget; void rebuild() { if (!_active || !_dirty) return; Element debugPreviousBuildTarget; // 这里调用的performRebuild方法, 在当前类并没有实现, 只能去自己的类里面查找实现 performRebuild(); } /// Called by rebuild() after the appropriate checks have been made. @protected void performRebuild(); }
梳理一下流程,如下:
这里创建了 StatelessElement ,创建成功后,framework 就会调用 mount方法,因为 StatelessElement 没有实现 mount,所以这里调用的是 ComponentElement 的 mount。
在 mount 中调用了 _firstBuild 方法进行第一次构建。(这里调用的是实现类(StatelessElement) 的 _firstBuild 方法)
_firstBuild 方法最后调用的是 super._firstBuild() ,也就是ComponentElement 的 _firstBuild 方法,在其中调用了 rebuild() 。由于 ComponentElement 没有重写,所以最终调用的是 Element 的 rebuild 方法。
rebuild 最终又会调用到 ComponentElement 的 performRebuild 方法中。 如下:
@override @pragma('vm:notify-debugger-on-exception') void performRebuild() { if (!kReleaseMode && debugProfileBuildsEnabled) Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent); assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true)); Widget? built; try { assert(() { _debugDoingBuild = true; return true; }()); // 调用 build ,这里调用的是实现类 StatelessElement 的,最终是调用到我们自己实现的 build 中 built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { // catch } finally { _dirty = false; assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false)); } try { //最终调用 updateChild 方法 _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { //.... _child = updateChild(null, built, slot); } if (!kReleaseMode && debugProfileBuildsEnabled) Timeline.finishSync(); }
@pragma('vm:prefer-inline') Element inflateWidget(Widget newWidget, Object? newSlot) { assert(newWidget != null); final Key? key = newWidget.key; if (key is GlobalKey) { final Element? newChild = _retakeInactiveElement(key, newWidget); if (newChild != null) { assert(newChild._parent == null); assert(() { _debugCheckForCycles(newChild); return true; }()); newChild._activateWithParent(this, newSlot); final Element? updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild); return updatedChild!; } } //创建对应的 element final Element newChild = newWidget.createElement(); assert(() { _debugCheckForCycles(newChild); return true; }()); // 调用 mount 方法 newChild.mount(this, newSlot); assert(newChild._lifecycleState == _ElementLifecycle.active); return newChild; }
上面代码中最终调用到了 updateChild 方法,这个方法在上面 Element的生命周期中有提到。
在 updateChild 方法中就会判断 built 是否需要更新或者替换,如果需要替换,就会清除原来的,并且对新的 built 创建 对应的 Element ,并且在最后调用 built 对应 Element 的 mount 方法。这里的 Element 就不一定是 StatelessElement 了,而是看在 build 方法中的 widget 对应的 Element 是什么。
总结一下
从上面的流程分析中我们可以看出来整个流程就像是一个环,最开始的framework 调用 mount 。在 mount 中最终调用到了 performRebuild ,在 performRebuild 中通过调用我们实现的 build 方法,拿到对应的 widget 后,如果需要替换,就会重新创建 widget 的 element,并且调用这个 element 的 mount 方法。
整个流程大致如上图所示
RenderObjectElement
我们以 Flex 为例来看一下是如何创建 Element 的。
@override MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
Fiex 是通过父类 SingleChildRenderObjectWidget 来创建的 Element,、MultiChildRenderObjectElement 是继承自 RenderObjectElement 的。
接下来我们浅析一下 RenderObjectElement的调用过程
class MultiChildRenderObjectElement extends RenderObjectElement { @override void mount(Element? parent, Object? newSlot) { //调用super.mount 将传入的 parent 插入到树中 super.mount(parent, newSlot); // final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false); Element? previousChild; //遍历所有的child for (int i = 0; i < children.length; i += 1) { //加载child final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild)); children[i] = newChild; previousChild = newChild; } _children = children; } } abstract class RenderObjectElement extends Element { @override void mount(Element? parent, Object? newSlot) { //将传入的 parent 插入到树中 super.mount(parent, newSlot); //创建与element相关联的renderObject 对象 _renderObject = widget.createRenderObject(this); //将element.renderObjectZ 插入到渲染树中的指定位置 attachRenderObject(newSlot); _dirty = false; } }