大良网站制作公司,crm管理系统开发语言,中国品牌策划网,hishop网站搬家一、Flutter常见的家族成员
Widget常见的家族成员 Element常见的家族成员 Render常见的家族成员 二、示例代码对应的Flutter Inspector树
示例代码#xff1a;MyApp-MyHomePage-ErrorWidget#xff0c;包含了StatelessWidget、StatefulWidget、LeafRenderObjectWid…一、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 启动的核心三步级联运算符调用
WidgetsFlutterBinding 初始化ensureInitialized()绑定根节点创建核心三棵树scheduleAttachRootWidget(app)绘制热身帧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绑定服务抽象类。GestureBindingFlutter 手势事件绑定处理屏幕事件分发及事件回调处理其初始化方法中重点就是把事件处理回调_handlePointerDataPacket函数赋值给 window 的属性以便 window 收到屏幕事件后调用window 实例是 Framework 层与 Engine 层处理屏幕事件的桥梁。SchedulerBindingFlutter 绘制调度器相关绑定类debug 编译模式时统计绘制流程时长等操作。ServicesBindingFlutter 系统平台消息监听绑定类。即 Platform 与 Flutter 层通信相关服务同时注册监听了应用的生命周期回调。PaintingBindingFlutter 绘制预热缓存等绑定类。SemanticsBinding语义树和 Flutter 引擎之间的粘合剂绑定类。RendererBinding渲染树和 Flutter 引擎之间的粘合剂绑定类内部重点是持有了渲染树的根节点。WidgetsBindingWidget 树和 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 RenderObjectWithChildMixinRenderBox//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 RenderObjectToWidgetAdapterRenderBox(//3、RenderObjectWithChildMixin类型继承自RenderObjectRenderObject继承自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 RenderObjectToWidgetElementRenderBox?);if (isBootstrapFrame) {//6、首帧主动更新一下匹配条件的情况下内部本质是调用SchedulerBinding的scheduleFrame()方法。//进而本质调用了window.scheduleFrame()方法。SchedulerBinding.instance!.ensureVisualUpdate();}}
}上面代码片段的步骤 2 和步骤 5 需要配合 RenderObjectToWidgetAdapter 类片段查看如下
//1、RenderObjectToWidgetAdapter继承自RenderObjectWidgetRenderObjectWidget继承自Widget
class RenderObjectToWidgetAdapterT extends RenderObject extends RenderObjectWidget {......//3、我们编写dart的runApp函数参数中传递的Flutter应用Widget树根final Widget? child;//4、继承自RenderObject来自PipelineOwner对象的rootNode属性一个Flutter App全局只有一个PipelineOwner实例。final RenderObjectWithChildMixinT container;......//5、重写Widget的createElement实现构建了一个RenderObjectToWidgetElement实例它继承于Element。 //Element树的根结点是RenderObjectToWidgetElement。overrideRenderObjectToWidgetElementT createElement() RenderObjectToWidgetElementT(this);//6、重写Widget的createRenderObject实现container本质是一个RenderView。//RenderObject树的根结点是RenderView。overrideRenderObjectWithChildMixinT 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只会创建一次后面都是直接复用的。*/RenderObjectToWidgetElementT attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElementT? 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 树的根结点是 RenderViewRenderView extends RenderObject with RenderObjectWithChildMixinRenderBox在 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的开发谈到Elementandroid的开发现在需要习惯这种指称默认的树指的就是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类型及其方法有些是自有方法有些是覆盖方法有些是基类方法这个时候只能一步步分析避免混乱。
RenderObjectToWidgetElement是RenderObjectToWidgetAdapter这个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对象创建的StateStatefulWidget(framework.dart:3989)再去创建子Widget的。我们的MyApp再去创建它的子Widget时就是通过此类方式因为MyApp是一个StatelessWidget对象MyApp创建的Element是StatelessElement类型。
再譬如RenderObjectElement在mount时还创建了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 RenderObjectWithChildMixinRenderObject提供的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 RenderObjectToWidgetAdapterRenderBox(container: renderView,debugShortDescription: [root],child: rootWidget,).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElementRenderBox?);...
}RenderObjectToWidgetElementT attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElementT? element ]) {...element createElement();...element!.mount(null, null);...return element!;
}创建RenderObjectToWidgetAdapter作为Widget树的根将传入的Widget挂上去调用RenderObjectToWidgetAdapter.createElement创建Element调用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参数传进去的
RenderObjectToWidgetAdapterRenderBox(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是一个递归的过程总结下来有下面几个步骤
Element如果是RenderObjectElement则创建RenderObject并从祖先找到上一个RenderObjectElement然后调用祖先RenderObjectElement的RenderObject的insertRenderObjectChild方法插入创建的RenderObject如果子widget需要build出来就调用build方法创建子widget如果不需要直接在成员变量可以拿到子widget调用子widget的createElement创建子Element调用子Element的mount方法将子Element的parent设置成自己然后子Element去到第1步
下面的动图展示了整个流程: 或者可以下载PPT查看