Widget纯作为一个配置文件存在,可以理解为一个数据结构。Element作为配置文件的实例化对象,具有生命周期的概念,承载构建上下文数据,且持有RenderObject,系统通过遍历Element来构建RenderObject数据,具体Layout,Paint交给RenderObject来完成。Element更像是一个中间层,隔开了Widget与RenderObject,同时也在正常开发过程中将开发者与RenderObject隔开,开发者大部分情况下只需要关注Widget即可。
Widget类和Element类一一对应。Element是通过Widget生成的 Widget是Element的配置数据,保存UI相关参数。Widget十分轻量,可以频繁的销毁和重建。
Widget.createElement()
创建Element对象
Widget.canUpdate(..)
1 2 3 4 static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }
复制
主要用于在Widget树重新build时复用旧的widget,其实具体来说,应该是:是否用新的Widget对象去更新旧UI树上所对应的Element对象的配置; 通过其源码我们可以看到,只要 newWidget 与 oldWidget 的 runtimeType和key 同时相等时就会用newWidget去更新Element对象的配置,否则就会创建新的Element。
State 一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存UI状态信息.
createState()
用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中可能会被多次调用。例如,当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例。createState()会在Element实例化的时候调用,所以,本质上就是一个StatefulElement对应一个State实例。
widget和context
widget, 它表示与该State实例关联的widget实例,由Flutter framework动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建 ,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
context, StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext。Elements继承自BuildContext
State如何被Widget复用 只有在Element实例化时会创建新的State。当Widget重新build,Element的updateChild会调用canUpdate(…)方法,若返回true,将child element的widget更新;若返回false,则会重新create新的element。
Element 最终的UI树其实是由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObject来完成的,从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
下面从Element的 挂载、更新、卸载 过程来描述Element
mount 根Element的mount
1 2 3 4 5 6 7 8 - main() - runApp(..) - WidgetsFlutterBinding.attachRootWidget(app) - RenderObjectToWidgetAdapter<RenderBox>.attachToRenderTree(buildOwner, renderViewElement); - createElement() - BuildOwner.buildScope(element, () { element.mount(null, null); })
复制
以上是根Element的mount过程,其中rederViewElement是根Elemet
** Element的mount过程
首先来看一下常用的StatelessElement,StatefulElement的继承关系
1 2 Element -> ComponentElement -> StatelessElement -> StatefulElement
复制
1 2 3 4 5 6 7 8 - mount() - Element.updateInheritance() - ComponentElement.firstBuild() - Element.rebuild() - ComponentElement.performRebuild() - built = build() -Widget.build() - Element.updateChild(child, built, _)
复制
Element.updateChild方法非常重要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @protected Element updateChild(Element child, Widget newWidget, dynamic newSlot) { if (newWidget == null ) { if (child != null ) deactivateChild(child); return null ; } if (child != null ) { if (child.widget == newWidget) { return child; } if (Widget.canUpdate(child.widget, newWidget)) { child.update(newWidget); return child; } deactivateChild(child); } return inflateWidget(newWidget, newSlot); } static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }
复制
根据上面的代码,总结各种case
child != null && newWidget == null : newWidget为空,说明 Widget.build返回了null, child element对应的widget已经不在widget树上,所以也要移除该element
child == null && newWidget != null : 第一次挂载child Element,解析build出的widget,调用 inflateWidget(newWidget, newSlot)
child != null && newWidget != null : 更新Element,StatefulWidget 调用setState时会进入这个分支。 会调用 child.canUpdate这个方法,判断是否需要更新Element。如果canUpdate返回false,移除这个element然后重新创建
child == null && newWidget == null : do nothing
在mount过程会进入第二个分支,调用 inflateWidget(newWidget, newSlot)
1 2 3 4 5 6 7 8 @protected Element inflateWidget(Widget newWidget, dynamic newSlot) { ... final Element newChild = newWidget.createElement(); newChild.mount(this , newSlot); return newChild; }
复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 abstract class StatefulWidget extends Widget { const StatefulWidget({ Key key }) : super (key: key); @override StatefulElement createElement() => StatefulElement(this ); State createState(); } class StatefulElement extends ComponentElement { StatefulElement(StatefulWidget widget) : _state = widget.createState(), super (widget) { _state._element = this ; _state._widget = widget; } @override Widget build() => state.build(this ); }
复制
StatefulWidget,State,StatefulElement的调用关系: Widget.createElement() -> Element.build() -> State.build()
更新Element 1 2 3 4 5 Widget.setState - Element .markNeedsBuild() - Element ._dirty = true - BuildOwner.scheduleBuildFor(this ) - BuildOwner._dirtyElements.add(element)
复制
当调用statState之后,会将element标脏,并加入到_dirtyElements列表中
1 2 3 - WidgetsBinding.drawFrame() - BuildOwner.buildScope(renderViewElement) - BuildOwner._dirtyElements[index].rebuild()
复制
每次UI刷新时,会回调WidgetBinding的drawFrame(), BuildOwner会将_dirtyElements列表rebuild。 可以翻上去查看挂载阶段rebuild之后的调用链,是完全一样的
卸载Element 在Element的updateChild方法中,在 child != null && newWidget == null
时,会调用deactivateChild()
1 2 3 4 5 6 @protected void deactivateChild(Element child) { child._parent = null ; child.detachRenderObject(); owner._inactiveElements.add(child); }
复制
deactivateChild()方法中,会将child element加入到 BuildOwner的_inactiveElements集合中。
还是WidgetBind.drawFrame()方法,会调用 buildOwner.finalizeTree(); 对不再使用的Element进行unmount
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void finalizeTree() { Timeline.startSync('Finalize tree' , arguments: timelineWhitelistArguments); try { lockState(() { _inactiveElements._unmountAll(); }); } ... } void _unmountAll() { _locked = true ; final List <Element > elements = _elements.toList()..sort(Element ._sort); _elements.clear(); try { elements.reversed.forEach(_unmount); } finally { _locked = false ; } } void _unmount(Element element) { element.visitChildren((Element child) { _unmount(child); }); element.unmount(); }
复制
RenderObject RenderObjectElement RenderObjectElement使用RenderObjectWidget作为配置文件。在RenderTree中有一个与之对应的RenderObject,用来执行具体的测量,绘制等操作。不是所有的Element都有对应的RenderObject
RenderObjectElement有三个常用的子类:
LeafRenderObjectElement:Leaf render objects, with no children
SingleChildRenderObjectElement:A single child
MultiChildRenderObjectElement:A linked list of children.
RenderObjectElement的mount、update、unmount逻辑与ComponentElement大致相同,只不过加上了RenderObject的相关逻辑。
mmount、update、unmount
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @override void mount(Element parent, dynamic newSlot) { super .mount(parent, newSlot); _renderObject = widget.createRenderObject(this ); attachRenderObject(newSlot); _dirty = false ; } @override void attachRenderObject(dynamic newSlot) { _slot = newSlot; _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot); final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement(); if (parentDataElement != null ) _updateParentData(parentDataElement.widget); } @override void update(covariant RenderObjectWidget newWidget) { super .update(newWidget); widget.updateRenderObject(this , renderObject); _dirty = false ; }
复制
RenderObject的更新 我们从render树的insert过程类分析RenderObject的更新 从RenderObjectElement.insertChildRenderObject开始
1 2 3 4 5 @override void insertChildRenderObject(RenderObject child, Element slot) { final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this .renderObject; renderObject.insert(child, after: slot?.renderObject); }
复制
这里调用了ContainerRenderObjectMixin的insert方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void insert(ChildType child, { ChildType after }) { adoptChild(child); _insertIntoChildList(child, after: after); } @override void adoptChild(RenderObject child) { setupParentData(child); markNeedsLayout(); markNeedsCompositingBitsUpdate(); markNeedsSemanticsUpdate(); super .adoptChild(child); } void markNeedsLayout() { if (_relayoutBoundary != this ) { markParentNeedsLayout(); } else { _needsLayout = true ; if (owner != null ) { owner._nodesNeedingLayout.add(this ); owner.requestVisualUpdate(); } } } @protected void markParentNeedsLayout() { _needsLayout = true ; final RenderObject parent = this .parent; if (!_doingThisLayoutWithCallback) { parent.markNeedsLayout(); } else { assert (parent._debugDoingThisLayout); } }
复制
和element的build流程不同,RenderObject的标脏会向上标脏。 找到 _relayoutBoundary 节点,触发owner._nodesNeedingLayout.add( this )
与owner.requestVisualUpdate()
其中owner._nodesNeedingLayout.add( this )将当前RenderObject注册进PipelineOwner的待刷新列表中,然后触发“VisualUpdate”。这里的owner是PipelineOwner
PiplelineOwner.requestVisualUpdate会请求engine进行一次刷新。。 (todo 不是标脏吗? 难道不是等待vsync信号对标脏的node进行重绘???) 来看一下PiplelineOwner.requestVisualUpdate
1 2 3 4 void requestVisualUpdate() { if (onNeedVisualUpdate != null ) onNeedVisualUpdate(); }
复制
onNeedVisualUpdate是在PiplelineOwner实例化的时候赋值的,在RendererBinding的initInstances里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void initInstances() { super .initInstances(); _instance = this ; _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); window ..onMetricsChanged = handleMetricsChanged ..onTextScaleFactorChanged = handleTextScaleFactorChanged ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged ..onSemanticsAction = _handleSemanticsAction; initRenderView(); _handleSemanticsEnabledChanged(); assert (renderView != null ); addPersistentFrameCallback(_handlePersistentFrameCallback); initMouseTracker(); } void _handlePersistentFrameCallback(Duration timeStamp) { drawFrame(); }
复制
ensureVisualUpdate 会调用window.scheduleFrame()请求native层绘制新帧。。。没太搞懂为什么要主动请求绘制
layout RendererBinding.drawFrame
1 2 3 4 5 6 7 8 9 @protected void drawFrame() { ... pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); renderView.compositeFrame(); pipelineOwner.flushSemantics(); }
复制
pipelineOwner.flushLayout()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void flushLayout() { try { while (_nodesNeedingLayout.isNotEmpty) { final List <RenderObject> dirtyNodes = _nodesNeedingLayout; _nodesNeedingLayout = <RenderObject>[]; for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) { if (node._needsLayout && node.owner == this ) node._layoutWithoutResize(); } } } finally { ... } }
复制
*RendererObject._layoutWithoutResize
1 2 3 4 5 6 7 8 9 10 void _layoutWithoutResize() { try { performLayout(); markNeedsSemanticsUpdate(); } catch (e, stack) { _debugReportException('performLayout' , e, stack); } _needsLayout = false ; markNeedsPaint(); }
复制
这里调用performLayout()进行layout,并标记需要paint
paint 之前说到过,layout过程中会对paint进行标脏
markNeedsPaint()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void markNeedsPaint() { if (_needsPaint) return ; _needsPaint = true ; if (isRepaintBoundary) { if (owner != null ) { owner._nodesNeedingPaint.add(this ); owner.requestVisualUpdate(); } } else if (parent is RenderObject) { final RenderObject parent = this .parent; parent.markNeedsPaint(); } else { if (owner != null ) owner.requestVisualUpdate(); } }
复制
和layout的标脏过程类似,一直向上标脏,直到 isRepaintBoundary 为true。注意,这里只有 RepaintBoundary 才加入到了 PiplineOwner 的_nodesNeedingPaint中,即最终是以 RepaintBoundary 单位进行刷新的 需要说明的是,我们可以 RepaintBoundary 是一个widget,我们可以手动添加,来终止paint的向上标脏过程,来达到局部重绘,提升性能。 LayoutBoundary 则是只是一个标记位,framework层帮我们判断的,我们不需要手动处理。
pipelineOwner.flushLayout()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void flushPaint() { try { final List <RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { if (node._needsPaint && node.owner == this ) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } } finally { ... } }
复制
与layout不同的是这里的 sort 规则是相反的,最深的节点先重绘。并且,多了判断 node._layer.attached
, 如果为true,重绘,并且重绘逻辑交给了PaintContext;如果为false,则执行 node._skippedPaintingOnLayer();
node._skippedPaintingOnLayer()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void _skippedPaintingOnLayer() { AbstractNode ancestor = parent; while (ancestor is RenderObject) { final RenderObject node = ancestor; if (node.isRepaintBoundary) { if (node._layer == null ) break ; if (node._layer.attached) break ; node._needsPaint = true ; } ancestor = node.parent; } }
复制
我的理解是,该节点的 layer 被 detached 了,需要对该节点及其父节点行重新标脏,保证该节点的 layer 被重新 attach 之后能够重绘。
PaintingContext.repaintCompositedChild(node);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) { _repaintCompositedChild( child, debugAlsoPaintedParent: debugAlsoPaintedParent, ); } static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false , PaintingContext childContext, }) { OffsetLayer childLayer = child._layer; if (childLayer == null ) { child._layer = childLayer = OffsetLayer(); } else { childLayer.removeAllChildren(); } childContext ??= PaintingContext(child._layer, child.paintBounds); child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); }
复制
又是 PaintContext 和 layer。。。先跳过这两个,最终重绘的逻辑在 child._paintWithContext(childContext, Offset.zero);
中
child._paintWithContext(childContext, Offset.zero);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void _paintWithContext(PaintingContext context, Offset offset) { if (_needsLayout) return ; RenderObject debugLastActivePaint; _needsPaint = false ; try { paint(context, offset); } catch (e, stack) { _debugReportException('paint' , e, stack); } }
复制