Flutter三棵树的创建流程

一、Flutter常见的家族成员

Widget常见的家族成员

Element常见的家族成员
Render常见的家族成员

二、示例代码对应的Flutter Inspector树

示例代码:MyApp->MyHomePage->ErrorWidget,包含了StatelessWidget、StatefulWidget、LeafRenderObjectWidget,其中StatelessWidget、StatefulWidget都属于组合Widget,它们通过build或者state.build返回自己的子节点,最后一级的ErrorWidget是LeafRenderObjectWidget,是个叶子节点,它下面没有子节点。

三、Flutter树根节点的创建和关联


从上图的创建流程可知,根节点RenderView是在RendererBinding初始化的时候创建的,创建时机最早,接下来调用WidgetsBinding.attachRootWidget方法创建Widget的根节点RenderObjectToWidgetAdapter对象,并把开发者自定义的根Widget MyApp挂载到RenderObjectToWidgetAdapter下面,接下来调用RenderObjectToWidgetAdapter.attachToRenderTree方法创建对应的RenderObjectToWidgetElement对象,这个对象就是Element树的根节点,在创建RenderObjectToWidgetElement对象时通过构造方法持有了RenderObjectToWidgetAdapter对象,然后调用RenderObjectToWidgetElement.mount方法持有了RenderView对象。这样Element就同时持有了其对应的Widget、Render。

四、Flutter树子节点的创建和关联

1. MyApp层级


在RenderObjectToWidgetElement.mount方法里,会继续调用RenderObjectToWidgetElement_rebuild->Element.updateChild->Element.inflateWidget,创建MyApp对应的StatelessElement对象,然后将该对象通过updateChild方法返回给上一级Element的子节点,在创建Element对象时通过构造方法持有了MyApp对象,这样Element就持有了Widget。MyApp是StatelessWidget不是RenderObjectWidget,所以MyApp没有对应的createRenderObject方法,StatelessElement是ComponentElement不是RenderObjectElement,其mount方法里也不会调用widget的createRenderObject方法,所以在这个层级,只有Widget节点(MyApp)和与其对应的Element节点(StatelessElement对象),没有对应的Render节点。

2. MyHomePage层级


在创建完MyApp对应的StatelessElement方法后,会调用其mount方法,然后经过一系列的方法调用,会调用到StatelessElement的build方法,然后调用MyApp的build方法,在这个build方法里会创建MyHomePage对象并返回,然后将MyHomePage作为参数继续调用Element.updateChild->Element.inflateWidget,然后创建MyHomePage对应的StatefulElement对象,然后将该对象通过updateChild方法返回给上一级Element的子节点,在创建Element对象时通过构造方法持有了MyHomePage对象,这样Element就持有了Widget。MyHomePage是StatefulWidget不是RenderObjectWidget,所以MyHomePage没有对应的createRenderObject方法,StatefulElement是ComponentElement不是RenderObjectElement,其mount方法里也不会调用widget的createRenderObject方法,所以在这个层级,只有Widget节点(MyHomePage)和与其对应的Element节点(StatefulElement对象),没有对应的Render节点。

3. ErrorWidget层级


在创建完MyHomePage对应的StatefulElement方法后,会调用其mount方法,然后经过一系列的方法调用,会调用到StatefulElement的build方法,然后调用_MyHomePageState的build方法,在这个build方法里会创建ErrorWidget对象并返回,然后将ErrorWidget作为参数继续调用Element.updateChild->Element.inflateWidget,然后创建ErrorWidget对应的LeafRenderObjectElement对象,然后将该对象通过updateChild方法返回给上一级Element的子节点,在创建Element对象时通过构造方法持有了ErrorWidget对象,这样Element就持有了Widget。LeafRenderObjectElement是RenderObjectElement,会调用ErrorWidget的createRenderObject方法创建renderObject对象(RenderErrorBox),然后将RenderErrorBox对象赋值给Element的_renderObject变量保存下来,然后调用attachRenderObject方法将renderObject插入到Render树里,同时Element也持有了Render对象。

LeafRenderObjectElement是叶子节点类型的Element,没有子节点了,调用mount创建完对应的Render后,执行就结束了,没有后续子节点的创建调用流程了,整个树的创建流程到这里就结束了,各级对应的Widget、Element、Render节点都创建关联完成。

五、示例代码生成的树结构


从上面的创建过程可知,整棵树的创建过程都是在Element的驱动下进行的,对于有子节点的Element,会递归调用Element.mount->Element.updateChild->Element.inflateWidget->创建下一级的Element对象->Element.mount->…递归循环创建整棵树。

在调用mount的过程如果ELement是RenderObjectElement类型的,还会为其创建对应的Render节点。

在整棵树的创建过程中发现,Widget对象创建完成后是保存到对应的Element上的,不会保存到上一级Widget上,Widget是没有直接的父子关系的,Widget这颗树可以理解为是虚拟的,是逻辑上存在的,它的树结构是通过Element实体树来反映的。


原文链接:https://blog.csdn.net/huideveloper/article/details/127710013


Flutter 的 runApp 与三棵树诞生流程源码分析

Flutter 程序入口

我们编写的 Flutter App 一般入口都是在 main 方法,其内部通过调用 runApp 方法将我们自己整个应用的 Widget 添加并运行,所以我们直接去看下 runApp 方法实现,如下:
 

/*** 位置:FLUTTER_SDK\packages\flutter\lib\src\widgets\binding.dart* 注意:app参数的Widget布局盒子约束constraints会被强制为填充屏幕,这是框架机制,自己想要调整可以用Align等包裹。* 多次重复调用runApp将会从屏幕上移除已添加的app Widget并添加新的上去,* 框架会对新的Widget树与之前的Widget树进行比较,并将任何差异应用于底层渲染树,有点类似于StatefulWidget
调用State.setState后的重建机制。*/
void runApp(Widget app) {WidgetsFlutterBinding.ensureInitialized()..scheduleAttachRootWidget(app)..scheduleWarmUpFrame();
}

可以看到上面三行代码代表了 Flutter 启动的核心三步(级联运算符调用):

  1. WidgetsFlutterBinding 初始化(ensureInitialized())
  2. 绑定根节点创建核心三棵树(scheduleAttachRootWidget(app))
  3. 绘制热身帧(scheduleWarmUpFrame())

WidgetsFlutterBinding 实例及初始化

直接看源码,如下:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {static WidgetsBinding ensureInitialized() {if (WidgetsBinding.instance == null)WidgetsFlutterBinding();return WidgetsBinding.instance!;}
}

WidgetsFlutterBinding 继承自 BindingBase,并且 with 了大量的 mixin 类。WidgetsFlutterBinding 就是将 Widget 架构和 Flutter Engine 连接的核心桥梁,也是整个 Flutter 的应用层核心。通过 ensureInitialized() 方法我们可以得到一个全局单例的 WidgetsFlutterBinding 实例,且 mixin 的一堆 XxxBinding 也被实例化。

BindingBase 抽象类的构造方法中会调用initInstances()方法,而各种 mixin 的 XxxBinding 实例化重点也都在各自的initInstances()方法中,每个 XxxBinding 的职责不同,如下:

  • WidgetsFlutterBinding:核心桥梁主体,Flutter app 全局唯一。
  • BindingBase:绑定服务抽象类。
  • GestureBinding:Flutter 手势事件绑定,处理屏幕事件分发及事件回调处理,其初始化方法中重点就是把事件处理回调_handlePointerDataPacket函数赋值给 window 的属性,以便 window 收到屏幕事件后调用,window 实例是 Framework 层与 Engine 层处理屏幕事件的桥梁。
  • SchedulerBinding:Flutter 绘制调度器相关绑定类,debug 编译模式时统计绘制流程时长等操作。
  • ServicesBinding:Flutter 系统平台消息监听绑定类。即 Platform 与 Flutter 层通信相关服务,同时注册监听了应用的生命周期回调。
  • PaintingBinding:Flutter 绘制预热缓存等绑定类。
  • SemanticsBinding:语义树和 Flutter 引擎之间的粘合剂绑定类。
  • RendererBinding:渲染树和 Flutter 引擎之间的粘合剂绑定类,内部重点是持有了渲染树的根节点。
  • WidgetsBinding:Widget 树和 Flutter 引擎之间的粘合剂绑定类。

从 Flutter 架构宏观抽象看,这些 XxxBinding 承担的角色大致是一个桥梁关联绑定,如下:

本文由于是启动主流程相关机制分析,所以初始化中我们需要关注的主要是 RendererBinding 和 WidgetsBinding 类的initInstances()方法,如下:

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {@overridevoid initInstances() {....../***1、创建一个管理Widgets的类对象*BuildOwner类用来跟踪哪些Widget需要重建,并处理用于Widget树的其他任务,例如管理不活跃的Widget等,调试模式触发重建等。*/_buildOwner = BuildOwner();//2、回调方法赋值,当第一个可构建元素被标记为脏时调用。buildOwner!.onBuildScheduled = _handleBuildScheduled;//3、回调方法赋值,当本地配置变化或者AccessibilityFeatures变化时调用。window.onLocaleChanged = handleLocaleChanged;window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;......}
}mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {@overridevoid initInstances() {....../*** 4、创建管理rendering渲染管道的类* 提供接口调用用来触发渲染。*/_pipelineOwner = PipelineOwner(onNeedVisualUpdate: ensureVisualUpdate,onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,);//5、一堆window变化相关的回调监听window..onMetricsChanged = handleMetricsChanged..onTextScaleFactorChanged = handleTextScaleFactorChanged..onPlatformBrightnessChanged = handlePlatformBrightnessChanged..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged..onSemanticsAction = _handleSemanticsAction;//6、创建RenderView对象,也就是RenderObject渲染树的根节点initRenderView();......}void initRenderView() {......//RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>//7、渲染树的根节点对象renderView = RenderView(configuration: createViewConfiguration(), window: window);renderView.prepareInitialFrame();}//定义renderView的get方法,获取自_pipelineOwner.rootNodeRenderView get renderView => _pipelineOwner.rootNode! as RenderView;//定义renderView的set方法,上面initRenderView()中实例化赋值就等于给_pipelineOwner.rootNode也进行了赋值操作。set renderView(RenderView value) {assert(value != null);_pipelineOwner.rootNode = value;}
}


到此基于初始化过程我们已经得到了一些重要信息,请记住 RendererBinding 中的 RenderView 就是 RenderObject 渲染树的根节点。上面这部分代码的时序图大致如下:


通过 scheduleAttachRootWidget 创建关联三棵核心树

WidgetsFlutterBinding 实例化单例初始化之后先调用了scheduleAttachRootWidget(app)方法,这个方法位于 mixin 的 WidgetsBinding 类中,本质是异步执行了attachRootWidget(rootWidget)方法,这个方法完成了 Flutter Widget 到 Element 到 RenderObject 的整个关联过程。源码如下:

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {@protectedvoid scheduleAttachRootWidget(Widget rootWidget) {//简单的异步快速执行,将attachRootWidget异步化Timer.run(() {attachRootWidget(rootWidget);});}void attachRootWidget(Widget rootWidget) {//1、是不是启动帧,即看renderViewElement是否有赋值,赋值时机为步骤2final bool isBootstrapFrame = renderViewElement == null;_readyToProduceFrames = true;//2、桥梁创建RenderObject、Element、Widget关系树,_renderViewElement值为attachToRenderTree方法返回值_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(//3、RenderObjectWithChildMixin类型,继承自RenderObject,RenderObject继承自AbstractNode。//来自RendererBinding的_pipelineOwner.rootNode,_pipelineOwner来自其初始化initInstances方法实例化的PipelineOwner对象。//一个Flutter App全局只有一个PipelineOwner实例。container: renderView, debugShortDescription: '[root]',//4、我们平时写的dart Widget appchild: rootWidget,//5、attach过程,buildOwner来自WidgetsBinding初始化时实例化的BuildOwner实例,renderViewElement值就是_renderViewElement自己,此时由于调用完appach才赋值,所以首次进来也是null。).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);if (isBootstrapFrame) {//6、首帧主动更新一下,匹配条件的情况下内部本质是调用SchedulerBinding的scheduleFrame()方法。//进而本质调用了window.scheduleFrame()方法。SchedulerBinding.instance!.ensureVisualUpdate();}}
}


上面代码片段的步骤 2 和步骤 5 需要配合 RenderObjectToWidgetAdapter 类片段查看,如下:

//1、RenderObjectToWidgetAdapter继承自RenderObjectWidget,RenderObjectWidget继承自Widget
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {......//3、我们编写dart的runApp函数参数中传递的Flutter应用Widget树根final Widget? child;//4、继承自RenderObject,来自PipelineOwner对象的rootNode属性,一个Flutter App全局只有一个PipelineOwner实例。final RenderObjectWithChildMixin<T> container;......//5、重写Widget的createElement实现,构建了一个RenderObjectToWidgetElement实例,它继承于Element。	     		//Element树的根结点是RenderObjectToWidgetElement。@overrideRenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);//6、重写Widget的createRenderObject实现,container本质是一个RenderView。//RenderObject树的根结点是RenderView。@overrideRenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;@overridevoid updateRenderObject(BuildContext context, RenderObject renderObject) { }/***7、上面代码片段中RenderObjectToWidgetAdapter实例创建后调用*owner来自WidgetsBinding初始化时实例化的BuildOwner实例,element 值就是自己。*该方法创建根Element(RenderObjectToWidgetElement),并将Element与Widget进行关联,即创建WidgetTree对应的ElementTree。*如果Element已经创建过则将根Element中关联的Widget设为新的(即_newWidget)。*可以看见Element只会创建一次,后面都是直接复用的。*/RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {//8、由于首次实例化RenderObjectToWidgetAdapter调用attachToRenderTree后才不为null,所以当前流程为nullif (element == null) {//9、在lockState里面代码执行过程中禁止调用setState方法owner.lockState(() {//10、创建一个Element实例,即调用本段代码片段中步骤5的方法。//调用RenderObjectToWidgetAdapter的createElement方法构建了一个RenderObjectToWidgetElement实例,继承RootRenderObjectElement,又继续继承RenderObjectElement,接着继承Element。element = createElement();assert(element != null);//11、给根Element的owner属性赋值为WidgetsBinding初始化时实例化的BuildOwner实例。element!.assignOwner(owner);});//12、重点!mount里面RenderObject owner.buildScope(element!, () {element!.mount(null, null);});} else {//13、更新widget树时_newWidget赋值为新的,然后element数根标记为markNeedsBuildelement._newWidget = this;element.markNeedsBuild();}return element!;}......
}

对于上面步骤 12 我们先进去简单看下 Element (RenderObjectToWidgetElement extends RootRenderObjectElement extends RenderObjectElement extends Element)的 mount 方法,重点关注的是父类 RenderObjectElement 中的 mount 方法,如下:

abstract class RenderObjectElement extends Element {//1、Element树通过构造方法RenderObjectToWidgetElement持有了Widget树实例。(RenderObjectToWidgetAdapter)。@overrideRenderObjectWidget get widget => super.widget as RenderObjectWidget;//2、Element树通过mount后持有了RenderObject渲染树实例。@overrideRenderObject get renderObject => _renderObject!;RenderObject? _renderObject;@overridevoid mount(Element? parent, Object? newSlot) {......//3、通过widget树(即RenderObjectToWidgetAdapter)调用createRenderObject方法传入Element实例自己获取RenderObject渲染树。//RenderObjectToWidgetAdapter.createRenderObject(this)返回的是RenderObjectToWidgetAdapter的container成员,也就是上面分析的RenderView渲染树根节点。_renderObject = widget.createRenderObject(this);......}
}


到这里对于 Flutter 的灵魂“三棵树”来说也能得出如下结论:

  • Widget 树的根结点是 RenderObjectToWidgetAdapter(继承自 RenderObjectWidget extends Widget),我们 runApp 中传递的 Widget 树就被追加到了这个树根的 child 属性上。
  • Element 树的根结点是 RenderObjectToWidgetElement(继承自 RootRenderObjectElement extends RenderObjectElement extends Element),通过调用 RenderObjectToWidgetAdapter 的 createElement 方法创建,创建 RenderObjectToWidgetElement 的时候把 RenderObjectToWidgetAdapter 通过构造参数传递进去,所以 Element 的 _widget 属性值为 RenderObjectToWidgetAdapter 实例,也就是说 Element 树中 _widget 属性持有了 Widget 树实例RenderObjectToWidgetAdapter 。
  • RenderObject 树的根结点是 RenderView(RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>),在 Element 进行 mount 时通过调用 Widget 树(RenderObjectToWidgetAdapter)的createRenderObject方法获取 RenderObjectToWidgetAdapter 构造实例化时传入的 RenderView 渲染树根节点。

上面代码流程对应的时序图大致如下:

结合上一小结可以很容易看出来三棵树的创建时机(时序图中紫红色节点),也可以很容易看出来 Element 是 Widget 和 RenderObject 之前的一个“桥梁”,其内部持有了两者树根,抽象表示如下:


以上就是三棵树的诞生流程.

热身帧绘制

到此让我们先将目光再回到一开始runApp方法的实现中,我们还差整个方法实现中的最后一个scheduleWarmUpFrame()调用,如下:

mixin SchedulerBinding on BindingBase {void scheduleWarmUpFrame() {......Timer.run(() {assert(_warmUpFrame);handleBeginFrame(null);});Timer.run(() {assert(_warmUpFrame);handleDrawFrame();//重置时间戳,避免热重载情况从热身帧到热重载帧的时间差,导致隐式动画的跳帧情况。resetEpoch();......if (hadScheduledFrame)scheduleFrame();});//在此次绘制结束前该方法会锁定事件分发,可保证绘制过程中不会再触发新重绘。//也就是说在本次绘制结束前不会响应各种事件。lockEvents(() async {await endOfFrame;Timeline.finishSync();});}
}

这段代码的本质这里先不详细展开,因为本质就是渲染帧的提交与触发相关,我们后边文章会详细分析 framework 层绘制渲染相关逻辑,那时再展开。在这里只用知道它被调用后会立即执行一次绘制(不用等待 VSYNC 信号到来)。

这时候细心的话,你可能会有疑问,前面分析 attachRootWidget 方法调用时,它的最后一行发现是启动帧则会调用window.scheduleFrame()然后等系统 VSYNC 信号到来触发绘制,既然 VSYNC 信号到来时会触发绘制,这个主动热身帧岂不是可以不要?

是的,不要也是没问题的,只是体验不是很好,会导致初始化卡帧的效果。因为前面window.scheduleFrame()发起的绘制请求是在收到系统 VSYNC 信号后才真正执行,而 Flutter app 初始化时为了尽快呈现 UI 而没有等待系统 VSYNC 信号到来就主动发起一针绘制(也被形象的叫做热身帧),这样最长可以减少一个 VSYNC 等待时间。

总结

上面就是 Flutter Dart 端三棵树的诞生流程,关于三棵树是如何互相工作的
 


flutter: 建树流程 

树的含义

当然是Element树!虽然对于熟悉以往界面开发的人来说这个结论有点让人狐疑,但我们应该明确的得到肯定:就是这样,因为从任意一个控件抽象Widget出发,无法到达Widget根节点或者任何Widget子节点,也就是无法实施遍历操作,当然也就不是树形数据结构了。对于Web开发的人来说比较容易接受,经常在涉及Web的开发谈到Element,android的开发现在需要习惯这种指称,默认的树指的就是Element树,否则理解就容易产生歧义,同时之前文章所说的Widget树这种说法是错误的,因为根本就没有Widget树!

如前文所述像RenderObjectToWidgetAdapter这样的Widget不就显式的持有了一个Widget作为child成员吗?的确,但这样的持有是具体类子类的持有,还是无法通过访问成员再访问到它的子节点,这个联系根本就是中断的。

所以建树就是建立Element树,访问Widget也只能通过Element间接访问:在Element定义中可以看到它直接持有了一个Widget,访问到了Element也就访问到了Widget,这是从android转过来的开发人员需要反复铭记的一点。Element有一个_parent作为其成员,因此可以上溯到根节点的Widget,然而令人困惑的是Element并没有Element数组或者列表来代表子节点!那Element是如何访问子节点的?

遍历子节点

基类Element并没有直接持有数组或者列表来访问子节点,而是通过visitChildren的空实现体方法,方法参数(ElementVisitor)本身是一个方法(typedef ElementVisitor = void Function(Element element); framework.dart:1794)。

这不就是个访问者模式吗,然而为什么要这么搞?这么做的意图是希望完全由Element子类型来决定访问Element子节点的顺序,为遍历操作提供更大的灵活性,子节点的持有还是需要的,只不过由Element子类型具体实现。这是可以想到的,显然,如果我们在基类型持有了子节点,那遍历子节点就有了默认顺序。譬如android中的ViewGroup, 从头到尾的子视图列表顺序代表了由下到上的层次关系(ZOrder),但不得不再提供类似getChildDrawingOrder方法来让子类型有改变访问顺序的机会。

遍历形式从直接持有变成方法传递,这样做也是有缺点和风险的,那就是可能在运行期动态的改变访问子节点的顺序而造成视图数据的紊乱!所以在这个方法上也有明确的注释说明访问顺序保持一致的重要性:

/// There is no guaranteed order in which the children will be visited, though
/// it should be consistent over time.

在建立树的过程中也不能调用这方法,因为访问的可能是旧的子节点或者子节点还没有完全建立。这样看来直接持有Element子节点未必就不好。

建立树的过程

Element对象是如何一步步构建成树形结构的?虽然在Element代码定义上有一些注释可以参考建树的关键步骤,但最好还是从入口调用分析来看:

WidgetsBinding.attachRootWidgetRenderObjectToWidgetAdapter.attachToRenderTreeBuildOwner.buildScopeRenderObjectToWidgetElement.mountRootRenderObjectElement.mount(null, null)RenderObjectElement.mountElement.mountRenderObjectWidget.createRenderObject => RenderObjectToWidgetAdapter.createRenderObjectRenderObjectToWidgetElement._rebuildElement.updateChildElement.inflateWidgetWidget.createElement => MyAppElement.mount

这里涉及了一大坨Element类型及其方法,有些是自有方法,有些是覆盖方法,有些是基类方法,这个时候只能一步步分析,避免混乱。

RenderObjectToWidgetElementRenderObjectToWidgetAdapter这个Widget具体创建的Element类型,显式的调用了mount方法,并且传入的参数均为(null, null),前面的文章已说明RenderObjectToWidgetElement是真正的Element根节点。关键是它是如何串连起其它Element对象的?

由以上调用序列可知RenderObjectToWidgetElement.mount最终调用了Element.moutElement.mout其实就是建立指向关系,但它是根节点,不用再指向父节点,只需要关注其子节点创建,再看是如何关联子节点的。RenderObjectToWidgetElement有一个显式的成员_child, 是一个Element类型,发现其是在RenderObjectToWidgetElement._rebuild中被赋值的,而_rebuild又是在RenderObjectToWidgetElement.mount的实现体中被调用,这样走到了一个关键方法Element.updateChild,从其注释就可以看出来:

This method is the core of the widgets system.

通过两个重要参数为null与否,Element.updateChild区分了4种具有不同含义的操作,当前只需关注child != null && newWidget != null这种情况,从其注释看这正是创建子节点的途径!细分的调用序列如下:

Element.updateChildElement.inflateWidgetWidget.createElement => MyAppElement.mount

针对child != null && newWidget != null这种情况Element.updateChild最终调用的是Element.inflateWidget,注意这个名称有误导性,从代码可知当前Element没有对Widget有任何操作,只是调用了Widget.createElement, 而这个Widget对象是从外部传入的,不是当前Element自己持有的!具体的,这个Widget对象应该是当前Element关联的Widget对象的子对象(widget.childwidgets/binding.dart:939),对应的正是我们自定义的MyApp!

所以新创建的子Element是由子Widget创建,接着又调用了子Element的mount方法,传入的parent参数是this(newChild.mount(this, newSlot); framework.dart:3084),即将当前Element作为父节点与新建节点Element关联,这个mount非常形象的表现了一个新建节点挂在一个即有节点之上的操作,于是子节点的mount继续以上过程直至建立最终的节点。

如此看来,flutter的Element更像是一个衣物挂钩,它建立的树形结构更像前向单链表网,而钩子正是Element._parent

再看Widget关联

最开始说Widget并不持有子Widget,那么Element在mount的时候当前Widget又是如何提供子Widget来创建子Element的呢?

答案是还是要看当前Element具体操作mount的方式。譬如我们的根ElementRenderObjectToWidgetElement直接用了自身持有的根WidgetRenderObjectToWidgetAdapter持有的child来关联了我们传入的MyApp作为子Widget。

再譬如一个比较重要的Element类型ComponentElement:它是在mount的时候调用了一个自身的抽象方法Widget build() (framework.dart:3950), 这里返回的Widget对象正是当前Element需要创建的子Widget。而ComponentElement有两个最重要的实现类覆盖了Widget build() 方法:StatelessElement是通过持有的StatelessWidget对象再去创建一个子Widget对象;StatefulElement是通过持有的StatefulWidget对象创建的State<StatefulWidget>(framework.dart:3989)再去创建子Widget的。我们的MyApp再去创建它的子Widget时就是通过此类方式,因为MyApp是一个StatelessWidget对象,MyApp创建的Element是StatelessElement类型。

再譬如RenderObjectElementmount时还创建了RenderObject,并且关联父RenderObject,而这个父RenderObject未必是父Element关联的RenderObject(_findAncestorRenderObjectElementframework.dart:4950);

所以大部分Widget的父子关系并不是持有关系而是创建关系,并且是在Element.mount的时机创建的,创建后也并不持有!

结论

建立Element树最重要的操作就是Element.mount

每一种具体类型的Element,实现了如何将当前Element挂接(mount)到父节点上的操作;这个挂接操作除了与父Element建立指向关系外,还规定了当前Element的一些其它属性的创建时机和操作。

创建一个Element最重要的操作就是Element.updateChild

更具体的是Element.inflateWidget方法;通过创建子Widget方式的不同,区分了两大类Element和Widget: (StatelessElement, StatelessWidget)和(StatefulElement, StatefulWidget)

所谓的Element树更像是前向单链表网,单链表有共同的表头。

父类Element不持有Element子节点,而是通过Element.visitChildren把遍历操作交给具体的Element子类型来实现。

但是RenderObject却像普通的单链表,因为通过mixin RenderObjectWithChildMixin<RenderObject>提供的child, RenderObject能够直接遍历子节点。


Flutter三棵树构建过程

flutter的渲染机制基本就是靠Widget、Element、RenderObject三棵树去实现的,这篇博客就来讲讲这三棵树是怎么创建的。

首先我们来看看这三者到底是个啥:

  • Widget: 描述一个UI元素的配置数据,不可变,修改信息需要重新new

  • Element: 通过Widget配置实例化出来的对象,它是可变的

  • RenderObject: 真正的渲染对象

让我们用一个简单的demo来做讲解:

void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',home: HelloWorldPage(),);}
}class HelloWorldPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("Hello World", style: TextStyle(color: Colors.blue)),);}
}

上面的代码正在屏幕的中间显示了一个Hello World字符串。

runApp

在main函数里面只有一行runApp调用,追踪下去我们可以看到它主要做了三件事情:

void runApp(Widget app) {WidgetsFlutterBinding.ensureInitialized()..scheduleAttachRootWidget(app)..scheduleWarmUpFrame();
}void scheduleAttachRootWidget(Widget rootWidget) {Timer.run(() {attachRootWidget(rootWidget);});
}void attachRootWidget(Widget rootWidget) {..._renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(container: renderView,debugShortDescription: '[root]',child: rootWidget,).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);...
}RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {...element = createElement();...element!.mount(null, null);...return element!;
}
  1. 创建RenderObjectToWidgetAdapter作为Widget树的根,将传入的Widget挂上去
  2. 调用RenderObjectToWidgetAdapter.createElement创建Element
  3. 调用Element.mount将它挂到Element树上,Element树的根节点的parent为null

Element.mount

Element的mount方法是三棵树创建流程的关键步骤,不同类型的Element mount的流程不太一样。

1.RenderObjectElement会创建RenderObject

如果Element是RenderObjectElement类型的,那么它对应的Widget一定是RenderObjectWidget类型的,这是它的构造函数决定的:

abstract class RenderObjectElement extends Element {RenderObjectElement(RenderObjectWidget widget) : super(widget);...
}

它在mount的时候会调用RenderObjectWidget.createRenderObject创建RenderObject然后将它挂到RenderObject树上:

RenderObject get renderObject => _renderObject!;RenderObject? _renderObject;void mount(Element? parent, Object? newSlot) {..._renderObject = widget.createRenderObject(this);...attachRenderObject(newSlot);...
}void attachRenderObject(Object? newSlot) {...// 插入RenderObject树_ancestorRenderObjectElement = _findAncestorRenderObjectElement();_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);...
}

这个_findAncestorRenderObjectElement方法比较魔性,找的是祖先RenderObjectElement,其实就是往parent一层层查找,直到找的RenderObjectElement:

RenderObjectElement? _findAncestorRenderObjectElement() {Element? ancestor = _parent;while (ancestor != null && ancestor is! RenderObjectElement)ancestor = ancestor._parent;return ancestor as RenderObjectElement?;
}

insertRenderObjectChild方法将创建的RenderObject插入成为祖先RenderObjectElement的RenderObject的子节点,这样就把创建的RenderObject挂到了RenderObject树上。

2.创建子Element并mount到Element树

处理完本节点的RenderObject之后,就会创建子Element将它的parent设置成自己,mount到Element树上。

Element都是通过Widget.createElement创建的,而Element会保存创建它的Widget。所以可以通过这个Widget去获取子Widget,然后用子Widget去创建子Element。

子Widget的获取有两种方式,如果是在Widget的构造函数传入的,那么直接可以拿到它,例如上面的RenderObjectToWidgetAdapter,然后用它去createElement创建子Element:

// 子widget是child参数传进去的
RenderObjectToWidgetAdapter<RenderBox>(container: renderView,debugShortDescription: '[root]',child: rootWidget,
)void mount(Element? parent, Object? newSlot) {..._rebuild();...
}void _rebuild() {...// widget.child拿到构造函数传进去的子widget,即rootWidget_child = updateChild(_child, widget.child, _rootChildSlot);...
}Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {...newChild = inflateWidget(newWidget, newSlot);...
}Element inflateWidget(Widget newWidget, Object? newSlot) {...// 创建子Elementfinal Element newChild = newWidget.createElement();...// 调用子Element的mount方法将它挂到Element树上,parent是第一个参数thisnewChild.mount(this, newSlot);...return newChild;
}

像StatelessWidget这种子widget是build出来的,则在mount的时候会调用它的build方法创建子widget,然后用它去createElement创建子Element:

void mount(Element? parent, Object? newSlot) {..._firstBuild();...
}void _firstBuild() {rebuild();
}void rebuild() {...performRebuild();...
}void performRebuild() {...built = build();//updateChild在上面也有追踪这里就不列出来了,内部调用了built.createElement创建子Element并返回_child = updateChild(_child, built, slot);...
}Widget build() => widget.build(this);

最终得到的三棵树大概长下面的样子,由于没有分成所以看上去是链表而不是树,但是这不影响我们理解,一旦某些节点有多个child节点就是输了:

Element通过widget成员持有Widget,如果是RenderObjectElement还通过renderObject成员持有RenderObject,可以看出来Element是连接Widget和RenderObject的桥梁。三个树的构建也都是通过递归mount Element去实现的。

当RenderObject树创建出来之后,Flutter的引擎就能遍历它去执行绘制将画面渲染出来了。

mount流程解析

从上面的代码可以看得出来,mount是一个递归的过程,总结下来有下面几个步骤

  1. Element如果是RenderObjectElement则创建RenderObject,并从祖先找到上一个RenderObjectElement,然后调用祖先RenderObjectElement的RenderObject的insertRenderObjectChild方法插入创建的RenderObject
  2. 如果子widget需要build出来就调用build方法创建子widget,如果不需要直接在成员变量可以拿到子widget
  3. 调用子widget的createElement创建子Element
  4. 调用子Element的mount方法将子Element的parent设置成自己,然后子Element去到第1步

下面的动图展示了整个流程:

或者可以下载PPT查看


Flutter视图原理之三棵树的建立过程

三棵树的关系

Flutter 中存在 Widget 、 Element 、RenderObject 三棵树,其中 Widget与 Element 是一对多的关系 ,Element 与 RenderObject 是一一对应的关系。

Element 中持有Widget 和 RenderObject , 而 Element 与 RenderObject 是一一对应的关系(除去 Element 不存在 RenderObject 的情况,如 ComponentElement是不具备 RenderObject),当 RenderObject 的 isRepaintBoundary 为 true 时,那么个区域形成一个 Layer,所以不是每个 RenderObject 都具有 Layer 的,因为这受 isRepaintBoundary 的影响。

Flutter 中 Widget 不可变,每次保持在一帧,如果发生改变是通过 State 实现跨帧状态保存,而真实完成布局和绘制数组的是 RenderObject , Element 充当两者的桥梁, State 就是保存在 Element 中。

一个可能的三棵树实例如下:


先看下widget继承关系:


对应的element继承关系:

通过上图可以看出,每个widget对应一个element类型,但是只有renderObjectElement类持有renderObject,只有renderObjectWidget才能创建render对象。因此flutter视图的三棵树结构,widget和element是一一对应的,但是renderObjectElement才对应一个render节点。

树的构建过程

flutter视图的入口是:

    runApp(const MyApp());class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(/....省略);}}

跟踪进入runApp函数:

void runApp(Widget app) {//1 final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();assert(binding.debugCheckZone('runApp'));//2binding..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))//3..scheduleWarmUpFrame();
}
  1. 第一步首先确认engine绑定flutter framework,是个阻塞过程
  2. scheduleAttachRootWidget 将app组件添加到根视图树上
  3. 向native平台层请求绘制一帧的信号

先看第二步:

  void attachRootWidget(Widget rootWidget) {final bool isBootstrapFrame = rootElement == null;_readyToProduceFrames = true;_rootElement = RenderObjectToWidgetAdapter<RenderBox>(container: renderView,debugShortDescription: '[root]',child: rootWidget,).attachToRenderTree(buildOwner!, rootElement as RenderObjectToWidgetElement<RenderBox>?);if (isBootstrapFrame) {SchedulerBinding.instance.ensureVisualUpdate();}}

RenderObjectToWidgetAdapter有两个成员,child:孩子widget,container:提供render功能容器,将rootWidget作为的child元素,renderView作为container。重点是attachToRenderTree函数,

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {if (element == null) {//。。。省略owner.buildScope(element!, () {element!.mount(null, null);});} else {//。。。省略}return element!;}


第一次element为空,那么进入第一个判断,首先调用buildScope,内部会回调element!.mount方法,

void buildScope(Element context, [ VoidCallback? callback ]) {if (callback == null && _dirtyElements.isEmpty) {return;}try {_scheduledFlushDirtyElements = true;callback();_dirtyElements.sort(Element._sort);_dirtyElementsNeedsResorting = false;int dirtyCount = _dirtyElements.length;int index = 0;while (index < dirtyCount) {final Element element = _dirtyElements[index];element.rebuild();index += 1;//。。。省略}return true;}());}}

首先回调callback,接着对脏元素集合调用rebuild方法。第一次肯定列表是空的,那么应该执行callback方法,也就进入了element.mount方法中,

  @overridevoid mount(Element? parent, Object? newSlot) {assert(parent == null);super.mount(parent, newSlot);_rebuild();assert(_child != null);}

mount方法也是个关键函数,函数的注释:

将此元素添加到给定父级的给定插槽中的树中。
当新创建的元素添加到
树是第一次。使用此方法初始化状态
取决于有父母。独立于父级的状态可以
更容易在构造函数中初始化。
此方法将元素从“初始”生命周期状态转换为
“活动”生命周期状态。
重写此方法的子类可能也希望重写
[update], [visitChildren], [RenderObjectElement.insertRenderObjectChild],
[RenderObjectElement.moveRenderObjectChild],以及
[RenderObjectElement.removeRenderObjectChild]。
此方法的实现应从调用继承的
方法,如 ‘super.mount(parent, newSlot)’。

注:其中newSlot参数,是parent renderObject的插槽位置对象,parent的子renderObject组成一个顺序列表或者是单个节点,插槽的结构是列表中的索引位置和前一个插槽元素的对象,parent将插槽传递给child保存起来,child可以确认自己在parent的布局位置。

首先调用_rebuild()方法,

RenderObjectToWidgetElement类

void _rebuild() {_child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
}


Element类

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {//。。。省略final Element newChild;if (child != null) {bool hasSameSuperclass = true;// When the type of a widget is changed between Stateful and Stateless via// hot reload, the element tree will end up in a partially invalid state.// That is, if the widget was a StatefulWidget and is now a StatelessWidget,// then the element tree currently contains a StatefulElement that is incorrectly// referencing a StatelessWidget (and likewise with StatelessElement).//// To avoid crashing due to type errors, we need to gently guide the invalid// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition// returns false which prevents us from trying to update the existing element// incorrectly.//// For the case where the widget becomes Stateful, we also need to avoid// accessing `StatelessElement.widget` as the cast on the getter will// cause a type error to be thrown. Here we avoid that by short-circuiting// the `Widget.canUpdate` check once `hasSameSuperclass` is false.//1bool hasSameSuperclass = true;final int oldElementClass = Element._debugConcreteSubtype(child);final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);hasSameSuperclass = oldElementClass == newWidgetClass;if (hasSameSuperclass && child.widget == newWidget) {// We don't insert a timeline event here, because otherwise it's// confusing that widgets that "don't update" (because they didn't// change) get "charged" on the timeline.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);// The [debugProfileBuildsEnabled] code for this branch is inside// [inflateWidget], since some [Element]s call [inflateWidget] directly// instead of going through [updateChild].newChild = inflateWidget(newWidget, newSlot);}} else {// The [debugProfileBuildsEnabled] code for this branch is inside// [inflateWidget], since some [Element]s call [inflateWidget] directly// instead of going through [updateChild].newChild = inflateWidget(newWidget, newSlot);}return newChild;}

1.updateChild函数(element的复用)

更新element对象分为下面几种情况:

  • child不为空

element的创建,首先判断旧的child element元素是否和新的widget元素class类型匹配,对应匹配关系如下:

1. 如果匹配hasSameSuperclass,并且element.widget和新传递进来的newWidget对象相同,那么说明widget是复用的(我们知道widget是不可变的,每次都要新建widget,所以在使用const定义的widget的情况下,widget使用的常量对象,符合这个判断),对child做slot更新,newChild更新为child。


2. 如果匹配hasSameSuperclass,并且widget.canUpdate判断成立,这个判断判断element对应的widget和newWidget对应的class类型和key是否相同(这种情况下,如果给widget定义了globalKey,并且参数使用const定义的话,那么判断是可以成立的),对child做slot更新,调用child.update(newWidget),newChild更新为child。

  static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}


2.1 child.update方法,首先更新element的widget,然后根据子类的覆写实现,statelessElement的实现是直接调用element的rebuild方法,statefullElement实现是更新state的widget并且回调
如果旧的element无法更新的话,需要解除绑定关系,将旧的element回收,用于后面的视图build复用;然后inflateWidget根据newWidget新建一个element。

  @protectedvoid deactivateChild(Element child) {assert(child._parent == this);child._parent = null;child.detachRenderObject();owner!._inactiveElements.add(child); // this eventually calls child.deactivate()}

child为空


inflateWidget根据newWidget新建一个element

2. inflateWidget函数
Element inflateWidget(Widget newWidget, Object? newSlot) {//。。。s省略try {final Key? key = newWidget.key;if (key is GlobalKey) {final Element? newChild = _retakeInactiveElement(key, newWidget);if (newChild != null) {newChild._parent == nullnewChild._activateWithParent(this, newSlot);final Element? updatedChild = updateChild(newChild, newWidget, newSlot);return updatedChild!;}}final Element newChild = newWidget.createElement();newChild.mount(this, newSlot);return newChild;}}
  1. 首先判断newWidget是否拥有globalKey,如果有的话尝试从复用池中获取拥有相同key的element元素,然后对该元素进行updateChild操作,这样又回到上面的起点了,这样看起来element更新是深度递归的。
  2. 如果没有key,那么直接创建一个新的element,然后element进行mount挂载操作,也就是将element加入到这个element树中。
3.mount函数

重点看看mount函数:

  void mount(Element? parent, Object? newSlot) {assert(_lifecycleState == _ElementLifecycle.initial);assert(_parent == null);assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);assert(slot == null);_parent = parent;_slot = newSlot;_lifecycleState = _ElementLifecycle.active;_depth = _parent != null ? _parent!.depth + 1 : 1;if (parent != null) {// Only assign ownership if the parent is non-null. If parent is null// (the root node), the owner should have already been assigned.// See RootRenderObjectElement.assignOwner()._owner = parent.owner;}assert(owner != null);final Key? key = widget.key;if (key is GlobalKey) {owner!._registerGlobalKey(key, this);}_updateInheritance();attachNotificationTree();}
  1. 首先对该widget进行参数判断,因为是新的widget,所以依赖关系都是空的,这里需要进行parent关联操作,生命周期设置,树的深度设置,owner是和parent使用的同一个,注册全局key。
  2. _updateInheritance,保存来自parent的_inheritedElements对象,这个对象集合里面包含着这棵树所有的inheritedElement(如果自己也是inheritedElement,也要将自己加入集合),这个类型的element具有从上下文继承数据的功能,可以根据类型读取数据,对应类型数据改变,可以通知所有依赖这个数据的inheritedElement去更新视图。
  3. attachNotificationTree,保存parent的_notificationTree集合,如果自己是NotifiableElementMixin类型,会将自己加入到parent._notificationTree集合中,这个集合是接受通知集合,一般的widget也不需要这个功能(不去深究)。

注: 从上面代码分析可知,mount是element从initial -> active的时间点。

mount是element基本接口,子类会对其进行复写,并且super调用

3.1 componentElement的实现

componentElement类,是负责组合子element的作用的,相当于Android View视图中的viewGroup,但是它也没有绘制功能,仅仅是负责排列组合子element。

component Element的复写:

  void mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);firstBuild();}void _firstBuild() {rebuild(); // This eventually calls performRebuild.}void rebuild({bool force = false}) {、//。。。省略if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {return;}//。。。省略try {performRebuild();}}

 会进行一次rebuild操作,内部直接调用的performRebuild()。

3.2 RenderObjectElement的实现

RenderObjectElement类,是起绘制作用的element,相当于Android View视图中的view,视图的正真的绘制操作都在里面实现。

RenderObjectElement复写:

  void mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);_renderObject = (widget as RenderObjectWidget).createRenderObject(this);attachRenderObject(newSlot);super.performRebuild(); // clears the "dirty" flag}

由RenderObjectWidget创建RenderObject,这个RenderObject是正真实现绘制功能的类。
attachRenderObject函数,主要是将parent.newSlot传递给自己,然后和parent建立关联,这个关联主要是renderTree的关联。

attachRenderObject函数

  void attachRenderObject(Object? newSlot) {assert(_ancestorRenderObjectElement == null);_slot = newSlot;_ancestorRenderObjectElement = _findAncestorRenderObjectElement();_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();if (parentDataElement != null) {_updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);}}

_ancestorRenderObjectElement

  RenderObjectElement? _findAncestorRenderObjectElement() {Element? ancestor = _parent;while (ancestor != null && ancestor is! RenderObjectElement) {ancestor = ancestor._parent;}return ancestor as RenderObjectElement?;}

向上递归获取第一个RenderObjectElement类型的祖先,然后将当前子renderObject插入到这个祖先RenderObjectElement的孩子中或者孩子队列中。

可以看出也不是所有的element都是RenderObjectElement类型的,componentElement以及它的子类就不是,那么就会被跳过继续向上递归。
进而也就有了文章开头的那三棵树的关系,widget和element是一对一关系的,build过程中,element创建一定需要widget去配置或者更新,renderObject的创建只有RenderObjectWidget才有这个接口功能,因此像componentElement类型的就没有renderObject。

SingleChildRenderObjectElement类的实现,这个类只包含一个单独的renderObject,直接赋值子child,:

  void insertRenderObjectChild(RenderObject child, Object? slot) {final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;assert(slot == null);assert(renderObject.debugValidateChild(child));renderObject.child = child;assert(renderObject == this.renderObject);}

MultiChildRenderObjectElement类的实现,这个类包含多个renderObject,按照自己定义的排列规则排列子renderObject,将child插入到子child队列中对应的位置,上面说了slot已经将child的位置定下来了,可以根据slot的位置,将child插入到指定位置。

  void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;assert(renderObject.debugValidateChild(child));renderObject.insert(child, after: slot.value?.renderObject);assert(renderObject == this.renderObject);}void insert(ChildType child, { ChildType? after }) {adoptChild(child);_insertIntoChildList(child, after: after);}void adoptChild(RenderObject child) {setupParentData(child);markNeedsLayout();markNeedsCompositingBitsUpdate();markNeedsSemanticsUpdate();child._parent = this;if (attached) {child.attach(_owner!);}redepthChild(child);}

adoptChild函数所作的事情是绑定parentData数据,child根据parent的布局数据layout的时候有用,接下来就是标记parent需要重新layout,child和parent进行关联。


void _insertIntoChildList(ChildType child, { ChildType? after }) {final ParentDataType childParentData = child.parentData! as ParentDataType;_childCount += 1;assert(_childCount > 0);if (after == null) {// insert at the start (_firstChild)childParentData.nextSibling = _firstChild;if (_firstChild != null) {final ParentDataType firstChildParentData = _firstChild!.parentData! as ParentDataType;firstChildParentData.previousSibling = child;}_firstChild = child;_lastChild ??= child;} else {final ParentDataType afterParentData = after.parentData! as ParentDataType;if (afterParentData.nextSibling == null) {// insert at the end (_lastChild); we'll end up with two or more childrenassert(after == _lastChild);childParentData.previousSibling = after;afterParentData.nextSibling = child;_lastChild = child;} else {// insert in the middle; we'll end up with three or more children// set up links from child to siblingschildParentData.nextSibling = afterParentData.nextSibling;childParentData.previousSibling = after;// set up links from siblings to childfinal ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;childPreviousSiblingParentData.nextSibling = child;childNextSiblingParentData.previousSibling = child;assert(afterParentData.nextSibling == child);}}}


插入操作,以下几种情况:

  1. 前驱为空,那么当前插入的child是队列的第一个元素,直接插入;
  2. 前驱不为空,如果前驱的next指针为空,那么前驱是尾部元素,child插入到尾部;
  3. 前驱不为空,并且前驱在队列中间,那么将child插入到前驱的后面。

parentDataElement

  ParentDataElement<ParentData>? _findAncestorParentDataElement() {Element? ancestor = _parent;ParentDataElement<ParentData>? result;while (ancestor != null && ancestor is! RenderObjectElement) {if (ancestor is ParentDataElement<ParentData>) {result = ancestor;break;}ancestor = ancestor._parent;}}


向上递归获取第一个parentDataElement类型的祖先,然后将当前子renderObject传递给祖先,祖先会验证自己的data和子child的data的数据是否一致,不一致会标记脏,下一帧会重新layout,否则不管。

  void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {if (applyParentData) {parentDataWidget.applyParentData(renderObject); }}void applyParentData(RenderObject renderObject) {//。。。s省略if (needsLayout) {markNeedsLayout();}}

其中实现了ParentDataWidget的子类有:

这些布局也说明了一个问题,子child的布局属性变化,会导致parent布局重新layout,这样好像会影响性能。

performRebuild函数
performRebuild也是element的基本接口,

  void performRebuild() {_dirty = false;}

不同的子类也会有不同的实现,component Element的复写:

  void performRebuild() {Widget? built;try {built = build();}try {_child = updateChild(_child, built, slot);}}Widget build();

  • 调用build方法,构建出本element所需要的widget组件;widget的build函数由子类实现提供,componentElement的子类主要有StatelessElement,StatefulElement,ProxyElement三个。
    1. StatelessElement使用child的widget来构建,Widget build() => (widget as StatelessWidget).build(this);
    2. ProxyElement直接使用child Widget build() => (widget as ProxyWidget).child;
    3. StatefulElement使用state来创建 Widget build() => state.build(this);
  • 调用updateChild,用新构建出的widget去更新旧的child element元素,这个updateChild方法上面已经讲过,可以知道,如果element的子element还有child的话,一直递归调用updateChild函数,可以推测出,element树的构建也是深度递归进行的。

总结三棵树创建流程

上面梳理了整个树创建的过程,调用链:updateChild -> inflateWidget -> mount -> performRebuild -> (child -> updateChild递归调用)

假如有以下widget树:

return Container(decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(8.0.r)),border: Border.all(color: CtrColor.lineRegular, width: 1),),child: const Row(children: [Image(image: AssetImage("static/images/ic_net_error.png")),Text("data")],));

可以画出整个树创建的流程图:


原文链接:https://blog.csdn.net/u012345683/article/details/133908260

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/177528.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

位运算与简单应用

一.位运算的基本概念&#xff1a; 首先&#xff0c;位运算是针对二进制的&#xff0c;(数字本来int,4字节,下面假设为1字节)。比如数字12&#xff0c;它的二进制本来是&#xff1a; 0000 0000 0000 0000 0000 0000 0000 1100 因为前面的数字大都是0&#xff0c;所以为了简写…

火影忍者游戏攻略大公开!成为忍者大师的秘诀揭秘

大家好&#xff01;作为火影忍者游戏的玩家&#xff0c;我们都希望能够在游戏中成为优秀的忍者大师&#xff0c;战胜强大的对手。为了帮助大家实现这一目标&#xff0c;我想分享一些实用的攻略和技巧。 首先&#xff0c;熟悉忍者技能是成为忍者大师的基础。在火影忍者游戏中&am…

C语言_自定义类型详解

文章目录 前言一.结构体的声明1.1结构体的基础知识1.2结构的声明1.3特殊声明1.4结构体的自引用在结构中包含一个类型为该结构本身的成员是否可以&#xff1f;正确的自引用方式匿名结构体类型和typedef的结合形式 1.5 结构体变量的定义和初始化结构体定义与初始化结构体里嵌套结…

【Linux进程】再谈软件—操作系统(Operator System)

目录 操作系统(Operator System) 概念 设计OS的目的 如何理解 "管理"——先描述再组织 系统调用和库函数概念 总结 操作系统(Operator System) 概念 任何计算机系统都包含一个基本的程序集合&#xff0c;称为操作系统(OS)。 笼统的理解&#xff0c;操作系统…

【python】路径管理+路径拼接问题

路径管理 问题相对路径问题绝对路径问题 解决os库pathlib库最终解决 问题 环境&#xff1a;python3.7.16 win10 相对路径问题 因为python的执行特殊性&#xff0c;使用相对路径时&#xff0c;在不同路径下用python指令会有不同的索引效果&#xff08;python的项目根目录根据执…

利用Graviton2和S3免费套餐搭建私人网盘

网盘是一种在线存储服务&#xff0c;提供文件存储&#xff0c;访问&#xff0c;备份&#xff0c;贡献等功能&#xff0c;是我们日常中不可或缺的一种服务。很多互联网公司都为个人和企业提供免费的网盘服务。但这些免费服务都有一些限制&#xff0c;比如限制下载速度&#xff0…

下载树莓派对应的64位Ubuntu系统步骤

说点废话&#xff1a;因为ros2需要安装在64位Ubuntu上面&#xff0c;所以安装64位最合适&#xff1b; 第一步打开https://cn.ubuntu.com/ 网站&#xff1b;选择下载--->iot----> 选择这个镜像文件下载。我觉得镜像文件是img格式的&#xff0c;跟iso文件区别是&#xff…

vue详细安装教程

这里写目录标题 一、下载和安装node二、创建全局安装目录和缓存日志目录三、安装vue四、创建一个应用程序五、3x版本创建六、创建一个案例 一、下载和安装node 官网下载地址&#xff1a;https://nodejs.org/en/download 选择适合自己的版本&#xff0c;推荐LTS&#xff0c;长久…

【计算机网络】计算机网络中的基本概念

文章目录 局域网LAN基于网线直连基于集线器组建基于交换机组建基于交换机和路由器组建 广域网WANIP地址端口号协议为什么要有协议知名协议的默认端口 五元组协议分层TCP/IP五层模型封装和分用 网络互连就是将多台计算机连接在一起&#xff0c;完成数据共享。数据共享本质是网络…

C++设计模式_23_Command 命令模式

我们将Command 和Visitor归为“行为变化”模式。 Command 命令模式与函数对象十分类似&#xff0c;但在C主流框架中&#xff0c;函数对象&#xff08;function object&#xff09;应用的更为广泛。 文章目录 1. “行为变化”模式1.1 典型模式 2. 动机( Motivation )3. 模式定义…

【Leetcode】【消失的数字】【C语言】

方法一&#xff1a;按位异或&#xff08;找单身狗&#xff09; 我们知道&#xff1a;按位异或^操作原则&#xff1a;相同为零&#xff0c;相异为一 所以 0^aa a ^a0 a ^bb ^a int missingNumber(int* nums, int numsSize){ int i 0; int tem1 0,tem20; for (i 0;i < nu…

大厂面试题-介绍一下自己对Netty

目录 用三点来简单的介绍下Netty&#xff1a; 面试官&#xff1a;哦&#xff0c;还不错&#xff0c;那你在说说为什么要用Netty&#xff1f; 面试官&#xff1a;那你在通俗地说一下Netty可以做什么事情&#xff1f; 面试官&#xff1a;那&#xff0c;在说说Netty有几种线程…

XUbuntu22.04之simplenote支持的Markdown语法总结(一百九十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

linux下df -h 命令一直卡住的解决方法

在Linux中&#xff0c;偶尔遇到用 df -h 查看磁盘情况时&#xff0c;一直卡住无法显示结果。 解决方法&#xff1a; 1、首先使用strace追踪到底执行到哪里卡住 $ strace df -h 2、如果没有strace命令则进行安装 $ yum install strace -y 3、显示出卡住的地方&#xff0c;如…

SpringBoot源码透彻解析—bean生命周期

先跟一段debug再看总结&#xff1a; 1 创建实例 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation&#xff08;自定义一个对象或者代理对象&#xff09;createBeanInstance&#xff08;创建实例&#xff09;MergedBeanDefinitionPostProcessor.postProcess…

编程怎么学才高效?初学编程怎么样才容易入门?

学习编程并提高编程能力需要一种结构化的方法&#xff0c;其中包括理解基础概念、实践、反馈和持续学习。以下是一些高效学习编程的策略&#xff1a; 理解基础概念&#xff1a;在学习编程的初期&#xff0c;理解基础概念非常重要。这包括学习编程语言的基本语法、数据类型、控…

Java调用HTTPS接口,绕过SSL认证

1&#xff1a;说明 网络编程中&#xff0c;HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是一种通过加密的方式在计算机网络上进行安全通信的协议。网络传输协议&#xff0c;跟http相比更安全&#xff0c;因为他加上了SSL/TLS协议来加密通信内容。 Java调…

算法与数据结构-分治算法

文章目录 什么是分治算法分治算法应用举例分析分治思想在海量数据处理中的应用 什么是分治算法 分治算法&#xff08;divide and conquer&#xff09;的核心思想其实就是四个字&#xff0c;分而治之 &#xff0c;也就是将原问题划分成 n 个规模较小&#xff0c;并且结构与原问…

JavaEE入门介绍,HTTP协议介绍,常用状态码及含义,服务器介绍(软件服务器、云服务器)

一、JavaEE入门 JavaEE&#xff08;Java Enterprise Edition&#xff09;&#xff0c;Java企业版&#xff0c;是一个用于企业级web开发&#xff08;不需要使用控制台&#xff09;平台。最早由Sun公司定制并发布&#xff0c;后由Oracle负责维护。 JavaEE平台规范了在开发企业级w…

3D RPG Course | Core 学习日记三:Navigation智能导航地图烘焙

前言 前面我们已经绘制好了一个简单的地图场景&#xff0c;现在我们需要使用Navigation给地图做智能导航&#xff0c;以实现AI自动寻路&#xff0c;以及设置地图的可行走区域以及不可行走区域&#xff0c;Navigation的基础知识、原理、用法在Unity的官方文档&#xff0c;以及网…