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( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement ); }
RenderObjectToWidgetAdapter继承自RenderObjectWidget,从RenderObject到Element树的桥梁。由runApp用于引导应用程序。
注意此时的renderViewElement 肯定是null。
RenderObjectToWidgetAdapter.attachToRenderTree
RenderObjectToWidgetElementattachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement 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。
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 localPostFrameCallbacks =
List.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.finalizeTree();
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.update(newWidget)来更新widget配置。
在上边重建流程中,如果子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; }