Flutter的Widget-Element-RenderObject
我的图片有些时候看得到水印 因为这个是我们的老师上课用的东西
- 如果觉得有些东西不清楚可以去看老师的微信公众号 coderwhy
- 这里只是把他上课的东西整理成笔记了
这个东西是flutter的渲染过程
这个东西虽然对我们代码没有直接关系 但是它可以让你写代码更加自信
这节课可以解决的问题
- BuildContext 是什么东西
- Widget频繁的创建 会不会影响性能
- State和StatefulWidget 的关系
- Key的用法
一. Flutter的渲染过程
1. 1 Widget-Element-RenderObject的关系
树结构 - 数据结构 它是一种非常重要的数据结构
我们使用flutter开发东西的时候 我们开发出来的东西的时候都是用Widget来进行搭建的
所以我们写的代码其实就是一个一个的Widget
他们之间存在一定的层级关系
比如MyApp就是我们的顶层 然后里面是HYHomePage 然后是其他的东西
这种结构就是一个树结构
所以这种Widget结构就是一个Widget树
我们运行flutter代码是基于Flutter SDK 这个里面有一个特别重要的东西
flutter Engine 它需要把我们的最终的界面 渲染成最终的界面
比如我们最后写了 一个Text那我们的flutter引擎是不是 就会解析这个Wiget然后把它显示到界面上
- 但是其实并不是
flutter Engine它并不直接渲染Widget tree
因为我们在flutter 中Widget tree 是特别不稳定的
因为它动不动就会重新调用build方法 一旦调用build方法
build -> Widget -> Widget -> Widget 而且中间会依赖很多的Widget 一旦build 下面所有的Widget 都会重新创建
意味着整个Widget树都是不稳定的
而且一build里面的布局啊那些东西都是会变化的 我们的flutter 性能是做不到很高的
所以这个东西并不是直接渲染Widget的
我们这里还有一个对应的树结构
- 叫做RenderObjectTree
我们真正渲染的Widget就是它们
但是你看这里Widget tree 和Render tree 并不是一一对应的关系的
这个是因为很多的Widget 它其实更多的是一个盒子的作用并不是一个可渲染的东西
甚至我们的Text 它是继承至StatelessWidget那我们就会看他的build方法
哪个RichText才是最终渲染的
然后我们再点RichText
这里它就继承至 xxRenderObjectWidget
继承制这种Widget才是flutter最终渲染的出来的Widget
Widget tree =》 Render tree 这样才能最终渲染出来
但是你进到Text , opacity, 甚至RichText里面它都是一些属性 它还是没有渲染的东西
渲染的时候我们需要做一个事 是要做layout 然后再做一个paint
layout -> paint
只要的你的东西是一个Widget它就没有任何layout相关的东西 也 没有任何和paint相关的东西
所以他们之间存在这样的关系
他们的这种关系其实借鉴react里面的虚拟dom
在早期的web的时候 你通过js生成的html代码 他是直接转换成真实的dom
但是这样它是非常消耗性能的, 它的代价是非常消耗性能的
如果我们的js里面改了一点点html的代码 我们就要操作dom这个是非常消耗性能的
- 所以React里面就提出了一个概念 虚拟dom的概念
- 意思是 加了一层东西
- 如果js中改了html代码 它就去改虚拟dom
这个虚拟dom就像一个中间商
如果你改了html代码 它就会生成一个新的虚拟dom和原来的来做一个对比
这里就会用到diff算法 (different) 然后它就会把原来的地方打一个patch(补丁)
现在这里flutter这里的三棵树就基本和react 虚拟dom是一样的
到时候 你如果进行了修改之类的 他就会对比之前的东西 类型是否一样 key值是否一样
如果都一样 那么我们完全没有必要更新我们的Element 也就是我们的我们RenderObject完全没有必要重新创建这个东西 (可能widget里面是重新创建了一个对象 但是Element只是更新一个属性就可以了) 同理和RenderObject
1.2 Widget 是什么
官方对Widget的说明:
- Flutter的Widgets的灵感来自React,中心思想是构造你的UI使用这些Widgets。
- Widget使用配置和状态,描述这个View(界面)应该长什么样子。
- 当一个Widget发生改变时,Widget就会重新build它的描述,框架会和之前的描述进行对比,来决定使用最小的改变(minimal changes)在渲染树中,从一个状态到另一个状态。
老师的理解:
- Widget就是一个个描述文件,这些描述文件在我们进行状态改变时会不断的build。
- 但是对于渲染对象来说,只会使用最小的开销来更新渲染界面。
1.3 Element是什么
官方对Element的描述:
- Element是一个Widget的实例,在树中详细的位置。
- Widget描述和配置子树的样子,而Element实际去配置在Element树中特定的位置。
1.4 RenderObject 是什么
官方对RenderObject的描述:
- 渲染树上的一个对象
- RenderObject层是渲染库的核心。
二. 对象的创建过程(这个创建的过程是否真会创建Element Render Widget树)
我们常见的几种构建的Widget在渲染的层面主要分成两种
- 组件Widget: 不会生成RenderObject
- Containr
- Text
- HYHomePage
- 渲染Widget: 会生成RenderObject
- Padding
- Row
我们现在点到Padding 里面
然后我们发现这个Padding它是继承至 SingleChildRenderObjectWidget
这个是一个XXRenderObject的形式
然后这个SingleChildRenderObjectWidget它又继承至谁呢
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget
而Container
Container -> StatelessWidget -> Widget
所以Widget有那些子类呢 StatelessWidget StatefulWidget然后就是RenderObjectWidget
这个RenderObjectWidget 它有一个方法 createRenderObject
也就是说到这里的时候它有一个核心的方法 createRenderObject
这里它是一个抽象方法 可见这个方法他是没有实现的 那就是在子类中实现的(其实并没有在这个子类中实现)
但是这个子类也没有实现
但是也没关系因为这个也是一个抽象类 所以我们到它的子类Padding里面去
Padding里面就有实现了
然后传入一些东西返回了一个东西 这个东西叫RenderPadding
然后它有继承关系 就点到父类去
再点进RenderBox里面
然后你就发现它继承至 RenderObject
- 继承关系:
- 方法关系:
Padding -> createRenderObject -> RenderPadding -> … -> RenderBox -> RenderObject
- 所以Padding这个类是在继承至RenderObjectWidget的方法createRenderObject
- createRenderObject方法中创建了一个 RenderOject对象
所以很可能这个地方它就创建了一个Render树上的对象
但是我们现在并没有看到这个东西的调用的地方
但是如果你去其他的Container 里面 它是没有这个creatRenderObject 这个方法来创建一个RenderObject对象的
那我们现在就要知道它在什么地方调用创建的这个RenderObject的
我们点到Padding里面发现这个里面 是没有这个createRenderObject相关的东西的
那我们就到它的父类SingleChildRenderObjectWidget
然后在父类里面有一个方法叫做 createElement 那这个方法是噶你什么的呢
我们来到 Widget
这个Widget它有一个抽象方法 createElement 这个东西是所有的Widget的父类
所以它所有的子类都是有这个方法的 它最终都是会实现这个方法的
createElement 方法 要么在某一个类里面实现 要么在子类中实现 要么在中间的某个类实现
每个类都会实现这个方法 只是说每个类实现的方式是不一样的
Padding虽然没有实现这个方法但是在它的父类SingleChildRenderObjectWidget是实现了这个方法的
那我们来看看Container里面的这个方法
Container里面是没有实现这个方法的
所以我们去它的父类去找
可见这个
所以我们可以下结论了 所有的Widget都会创建Element对象
不论它是渲染Widget还是组件Widget
但是这边创建的Element对象是不一样的
我们看到这个StatelessWidget 的方法 创建的Element对象叫做 StatelessElement
如过现在到StatefulWidget里面去看
它创建的是这个Element
但是它们是有相似的地方的
你会发现他们都是继承至ComponentElement
- 它们的本质是一样的 它们都是继承至ComponentElement这个Element的
但是StatefulWidget会比StatelessWidget多一些属性
- StatelessWidget
- StatefulWidget -> State
同样的Padding 父类SingleChildRenderObjectWidget 也有这个方法 同样也会创建Element对象
但是 渲染Widget 和 组件Widget 他们是不一样的
- 写Widget
- 某些Widget会创建RenderObject
- 所有的Widget都会创建Element对象
- mount 方法
这个方法是flutter Engine会调用的方法
我们点到ElementWidget里面去
然后点到它的根Widget Element里面去
这个地方有一个mount方法
所有的widget都有element 同时element的根有mount这个方法
- 写Widget
- 某些Widget会创建RenderObject
- 所有的Widget都会创建Element对象
- mount 方法:
我们来到这个StatelessElement
但是它没有这个mount方法
所以它的父类必然是有这个方法的
然后你注意到它执行了一个_fristBuild方法
我们点进去
然后这里执行了一个performRebuild方法
然后就发现这个东西没有实现
那我们就要看它的实现 我们需要去看它的Components的实现 因为我们是从Components点过来的嘛
这里就需要看大量的源码
然后它这里干了一件事 它调用了一下这个build方法
这个build() 结果就是 Widget
也就是说我们的这个Element 里面build
然后再点进去
这个东西是一个抽象的方法
所以我们再找它的实现方法
同样我们找 StateElement里的这个方法
然后发现这个东西它掉了一个下Widget的build方法
- 所以你看创建Element系统在调mount方法的时候
- 它调用了_fristBuild -> rebuild -> performBuild -> build -> widget.build
我猜这里就是将Widget和Element链接起来了
那这个widget 是谁呢
我们来到这个StatelessWidget里面的createElement
我们发现这个地方我们创建StatelessWidget的时候把this传进去了
这个this 是谁 这个this在Widget里面那这个this就是一个widget
我们点进去
发现这个东西传给父类了
同理
然后我们就看到在根的Element里面这个东西_widget被赋值为传入的widget
所以传入的widget 是不是就是创建的widget啊
_fristBuild -> rebuild -> performBuild -> build -> widget.build
我们之前不是说 所有的widget创建以后都会调用自己的build方法
在哪调用的就是在这调用的
为什么每次我们的widget创建出来以后它会调用build方法呢
就是这样一步一步的调用的
- 整理一下
我们在 所有的Widget的父类它有一个方法createElement
我们来到StatelessWidget的找到它的createElement 发现它是创建了一个StatelessElement然后再到它的父类ComponentElement
发现这个父类里面有一个mount 方法 这个mount比较关键
它里面调用了一个_fristBuild -> rebuild 这个方法也调用了Component方法里面的rebuild方法 我们再点到rebuild方法
发现这个方法来自于Element rebuild方法里面调用了performRebuild 方法 这个方法同样来自Element 但是它是一个抽象方法 所以我们去找它的子类
我们又来到ComponentElement它实现了这个方法 这个方法里面又一个重要的方法build 我们再点到build里面