flutter_5_深入_1_深入widget树和构建流程
Build流程
BuildOwner.buildScope() 会有两种调用时机:
- 树构建(应用启动时):runApp() 方法调用的 scheduleAttachRootWidget() 方法,它会构建Widgets Tree、Element Tree与RenderObject Tree三棵树。
- 树更新(帧绘制与更新时):这里不会重新构建三棵树,而是只会更新dirty区域的Element。
也即是说树的构建和更新都是由 BuildOwner.buildScope() 方法来完成的。它们的差别在于树构建的时候传入了一个 element.mount(null, null) 回调。在 buildScope() 过程中会触发这个回调。
这个回调会构建三棵树,为什么会有三棵树呢,因为Widget只是对UI元素的一个抽象描述,我们需要先将其inflate成Element,然后生成对应的RenderObject来驱动渲染,如下所示:
- Widget Tree:为Element描述需要的配置,调用createElement方法创建Element,决定Element是否需要更新。Flutter通过查分算法比对Widget树前后的变化,来决定Element的State是否改变。
- Element Tree:表示Widget Tree特定位置的一个实例,调用createRenderObject创建RenderObject,同时持有Widget和RenderObject,负责管理Widget的配置和RenderObjec的渲染。Element的状态由Flutter维护,开发人员只需要维护Widget即可。
- RenderObject Tree:RenderObject绘制,测量和绘制节点,布局子节点,处理输入事件。
首次
整个流程挺简单的,就从runApp方法一直往下跟就可以了。
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) ..scheduleWarmUpFrame(); }
WidgetsBinding.attachRootWidget
void attachRootWidget(Widget rootWidget) { _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>); }
RenderObjectToWidgetAdapter继承自RenderObjectWidget,从RenderObject到Element树的桥梁。由runApp用于引导应用程序。
注意此时的renderViewElement 肯定是null。
RenderObjectToWidgetAdapter.attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element != null); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); // This is most likely the first time the framework is ready to produce // a frame. Ensure that we are asked for one. SchedulerBinding.instance.ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element; }
createElement创建的element是RenderObjectToWidgetElement,他是一个RootRenderObjectElement,也就是根element。
element.mount(null, null);会向下遍历并构建整个widget树。
RenderObjectToWidgetElement.mount
@override void mount(Element parent, dynamic newSlot) { assert(parent == null); super.mount(parent, newSlot); _rebuild(); }
RenderObjectToWidgetElement._rebuild
void _rebuild() { try { _child = updateChild(_child, widget.child, _rootChildSlot); } catch (exception, stack) { } }
此时的widget就是RenderObjectToWidgetAdapter,它的widget.child就是runApp传递进去的widget。
Element.updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { 。。。 Element newChild; if (child != null) { 。。。 } else { newChild = inflateWidget(newWidget, newSlot); } return newChild; }
由于是第一次,Element child是null,执行else里的逻辑,inflateWidget使用子widget来创建一个子Element。
此时的newWidget是runApp传递进去的widget。
Element.inflateWidget
Element inflateWidget(Widget newWidget, dynamic 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); newChild._activateWithParent(this, newSlot); final Element updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild); return updatedChild; } } final Element newChild = newWidget.createElement(); newChild.mount(this, newSlot); return newChild; }
有两步,
1. 如果是GlobalKey的话,会先从GlobalKey中获取引用的Element,如果有 有效的element的话就复用,
2. 如果 不是GlobalKey 或 没有从GlobalKey中获取到element 的话,就用widget调用其createElement()来创建了一个element,接着就把新建的子element挂载到了element树上。
element.mount主要有两个流程,
- 一个是ComponentElement的,
- 一个是RenderObjectElement的,
ComponentElement.mount
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_active); _firstBuild(); assert(_child != null); }
super.mount会把parent记录在此element中。
firstBuild会调用到performRebuild方法:
void performRebuild() { Widget built; try { built = build(); } catch (e, stack) { } finally { } try { _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { } }
此方法会首先调用build方法,来创建其子widget,ComponentElement主要有两类stateful和stateless,会有不同的实现。
然后就又调用到了updateChild方法,这就回到了上边流程,所以其实就是一直往下遍历创建widget树。
RenderObjectElement.mount
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); attachRenderObject(newSlot); _dirty = false; }
此方法会创建一个RenderObject,
之后就会把此RenderObject添加到RenderObject树上。
attachRenderObject
void attachRenderObject(dynamic newSlot) { assert(_ancestorRenderObjectElement == null); _slot = newSlot; _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot); final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement(); if (parentDataElement != null) _updateParentData(parentDataElement.widget); }
1. _findAncestorRenderObjectElement往上找离它最近的父RenderObjectElement,这个和_parent可能不一样;然后把当前的renderObject给插入到父RenderObjectElement中。
2. 接着_findAncestorParentDataElement(),它的作用是找到离它最近的父ParentDataElement,并用ParentDataWidget上的数据更新此renderObject上的ParentData数据(具体作用可以看flutter2_widget_6_其他.docx 中的ParentDataWidget)。
状态更新时
标记element dirty
一般状态更新是StatefulWidget才可以的,所以这里分析StatefulWidget。
我们通过调用State.setState来标识当前需要更新子widget。
void setState(VoidCallback fn) { final dynamic result = fn() as dynamic; _element.markNeedsBuild(); }
可以看到会先执行传递进来的函数类型参数,然后调用markNeedsBuild来标记此element需要更新其子widget。
void markNeedsBuild() { if (!_active) return; if (dirty) return; _dirty = true; owner.scheduleBuildFor(this); }
此方法首先会判断是否已经调用过了,_dirty会在之后的rebuild后重新设置为false。
owner是BuildOwner,用来管理widget框架。此类用于追踪需要rebuilding的widget,并作为一个整体处理应用于widget树的其他任务,比如管理树的 inactive element列表,以及在调试时hot reload期间在必要时触发“reassemble”命令。
一般BuildOwner是由WidgetsBinding持有,并且与 build/layout/paint管道的其余部分一起被操作系统驱动。额外的BuildOwner可以被构建来管理off-screen widget trees。
要把一个BuildOwner分配给一个Element树,请使用RootRenderObjectElement.assignOwner 方法。
BuildOwner.scheduleBuildFor
void scheduleBuildFor(Element element) { if (!_scheduledFlushDirtyElements && onBuildScheduled != null) { _scheduledFlushDirtyElements = true; onBuildScheduled(); } _dirtyElements.add(element); element._inDirtyList = true; }
首先调用onBuildScheduled()方法,此方法是一个回调,在WidgetsBinding中赋值的,
然后把此element加入到了_dirtyElements列表中了。
WidgetsBinding.initInstances
void initInstances() { super.initInstances(); _instance = this; _buildOwner = BuildOwner(); buildOwner.onBuildScheduled = _handleBuildScheduled; }
回到上边onBuildScheduled()的调用,会调用到ensureVisualUpdate()
void _handleBuildScheduled() { ensureVisualUpdate(); } void ensureVisualUpdate() { switch (schedulerPhase) { case SchedulerPhase.idle: case SchedulerPhase.postFrameCallbacks: scheduleFrame(); return; case SchedulerPhase.transientCallbacks: case SchedulerPhase.midFrameMicrotasks: case SchedulerPhase.persistentCallbacks: return; } } void scheduleFrame() { if (_hasScheduledFrame || !framesEnabled) return; ensureFrameCallbacksRegistered(); window.scheduleFrame(); _hasScheduledFrame = true; }
最后会去调用底层window.scheduleFrame()来注册一个下一帧时回调,就类似于Android中的ViewRootImpl.scheduleTraversals()。
重建dirty Element的子widget
下一帧来到后,会调用到的方法是在上边的ensureFrameCallbacksRegistered()中注册的回调,
SchedulerBinding.ensureFrameCallbacksRegistered
void ensureFrameCallbacksRegistered() { window.onBeginFrame ??= _handleBeginFrame; window.onDrawFrame ??= _handleDrawFrame; }
onBeginFrame :主要是用来执行动画,
onDrawFrame :这个主要处理上边说的persistentCallbacks。
void handleDrawFrame() { try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; for (final FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!); // POST-FRAME CALLBACKS _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (final FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!); } finally { } }
看下persistentCallbacks列表在哪添加的callback
最终找到是在RendererBinding.initInstances中添加的callback,
void initInstances() { super.initInstances(); _instance = this; _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); initRenderView(); addPersistentFrameCallback(_handlePersistentFrameCallback); initMouseTracker(); }
WidgetsFlutterBinding继承了RendererBinding。
接着上边handleDrawFrame的流程:
void _handlePersistentFrameCallback(Duration timeStamp) { drawFrame(); _scheduleMouseTrackerUpdate(); }
WidgetBinding重载了drawFrame,把build流程加入进来了。
@override void drawFrame() { try { if (renderViewElement != null) buildOwner.buildScope(renderViewElement); super.drawFrame(); buildOwner.finalizeTree(); } finally { } }
buildOwner.buildScope
void buildScope(Element context, [ VoidCallback callback ]) { if (callback == null && _dirtyElements.isEmpty) return; try { _scheduledFlushDirtyElements = true; if (callback != null) { assert(_debugStateLocked); Element debugPreviousBuildTarget; _dirtyElementsNeedsResorting = false; try { // 可以添加一个回调在build之前执行。 callback(); } finally { } } _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) { element._inDirtyList = false; } _dirtyElements.clear(); _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; } }
可以看到会遍历dirtyElements列表中的element.rebuild()。
而element.rebuild()最终会调用到performRebuild()。
Element.performRebuild
接着会根据不同类型的element去 重建子widget 或 重建子element。
ComponentElement.performRebuild
void performRebuild() { Widget built; try { built = build(); } catch (e, stack) { } 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 { _child = updateChild(_child, built, slot); } catch (e, stack) { } }
会先调用build创建一个子widget,然后调用Element.updateChild来更新。
RenderObjectElement.performRebuild
void performRebuild() { widget.updateRenderObject(this, renderObject); _dirty = false; }
可以看到RenderObjectElement只是更新widget的配置。
Element.updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { if (newWidget == null) { if (child != null) deactivateChild(child); return null; } Element newChild; 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)) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); newChild = child; } else { deactivateChild(child); newChild = inflateWidget(newWidget, newSlot); } } else { newChild = inflateWidget(newWidget, newSlot); } return newChild; }
由于不是第一次调用,所以Element child不是null,进入if逻辑,会进行三个判断:
- 如果子widget不变,子element和子widget匹配(就是是否都是stateless,或都是stateful),那么更新slot。
- 如果子element和子widget匹配,但子widget发生了变化,就调用子element.update(newWidget)来更新widget配置。
- 最后一个判断是子element和子widget不匹配,那么就把老的child element加入到一个_inactiveElements列表中,然后进行重建element。
element.update
@mustCallSuper void update(covariant Widget newWidget) { _widget = newWidget; }
会把newWidget记录下来,
ComponentElement.update
- StatelessElement.update
@override void update(StatelessWidget newWidget) { super.update(newWidget); assert(widget == newWidget); _dirty = true; rebuild(); }
最后又调用rebuild,rebuild中会调用performRebuild()去重建其子widget。
- StatefulElement.update
@override
@override void update(StatefulWidget newWidget) { super.update(newWidget); assert(widget == newWidget); final StatefulWidget oldWidget = _state._widget; // Notice that we mark ourselves as dirty before calling didUpdateWidget to // let authors call setState from within didUpdateWidget without triggering // asserts. _dirty = true; _state._widget = widget as StatefulWidget; try { final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic; } finally { } rebuild(); }
回调state.didUpdateWidget,
最后又调用rebuild,rebuild中会调用performRebuild()去重建其子widget。
RenderObjectElement.update
RenderObjectElement.update
@override void update(covariant RenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); widget.updateRenderObject(this, renderObject); _dirty = false; }
可以看下RenderObjectElement的几个重要的子类:
- SingleChildRenderObjectElement.update
void update(SingleChildRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); _child = updateChild(_child, widget.child, null); }
- MultiChildRenderObjectElement.update
void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren); _forgottenChildren.clear(); }
unmount流程
在上边重建流程中,如果子element和子widget不匹配,那么就把老的child element加入到一个_inactiveElements列表中。
为什么要先加入到这里边,而不是直接unmount呢?
这么做的原因一般是element的parent发生了改变,需要先从原来的树上移出(也就是加入到_inactiveElements中),然后在此帧后续的操作中如果真正需要复用时从列表中再取出,如果后续不需要那么就会在整个构建流程执行完时,框架会把这个列表中的element都给unmount了。
经过搜索发现只有GlobalKey使用了这一逻辑。
具体逻辑在,BuildOwner.finalizeTree()
void finalizeTree() { Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent); try { lockState(() { _inactiveElements._unmountAll(); // this unregisters the GlobalKeys }); } catch (e, stack) { // Catching the exception directly to avoid activating the ErrorWidget. // Since the tree is in a broken state, adding the ErrorWidget would // cause more exceptions. _debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack); } finally { Timeline.finishSync(); } }
这个逻辑就是每帧的处理流程说的The finalization phase。
对于StatefulElement的unmount,里边会调用state.dispose():
void unmount() { super.unmount(); _state.dispose(); _state._element = null; _state = null; }