1、UI 编程的两种方式
目前主流的UI编写方式主要有两种,命令式UI和声明式UI。在iOS开发中使用的UI编程方式是命令式UI(Imperative UI),而声明式UI的意思就是让开发者描述需要一个什么样的界面。在Flutter中就是采用了声明式UI(Declarative UI)
下面这样一个简单的例子:
在iOS中代码需要这么写
viewB.backgroundColor = [UIColor red];
for (UIView *view in viewB.subviews) {
[view removeFromSuperview];
}
UIView *viewC3 = [[UIView alloc] initWithFrame:CGRectMake(100,80,120,40)];
[viewB addSubview:viewC3];
在Flutter声明式UI中只需要这样
return ViewB(
color: red,
child: ViewC(...),
)
声明式 UI 相对来说减轻了开发者的负担,不需要考虑如何调用 UI 实例的方法来改变不同的状态,只需要开发者描述当前的 UI 状态 (即各属性的值),框架会自动将 UI 从之前的状态切换到开发者描述的当前状态。
2、Flutter渲染三棵树
在官方文档中对Widget描述如下:
/// Describes the configuration for an [Element].
///
/// Widgets are the central class hierarchy in the Flutter framework. A widget
/// is an immutable description of part of a user interface. Widgets can be
/// inflated into elements, which manage the underlying render tree.
主要包含以下几个信息
Widget是指一部分 UI 的描述Widget是不可变的Widget是对Element配置的描述,而Element管理着底层的渲染树
第一个信息显而易见,各个Widget会根据我们所写的属性 (在 Flutter 里指状态 State) 展示在屏幕上。那么第二个信息,如果Widget是不可变的,而且用的是声明式 UI,那么随着用户的操作或者数据的变更,UI 是怎么更新的
Flutter 渲染树
简介
在Flutter中,除了
Widget树,还有Element树和Render树,这三棵树各司其职,完成了Flutter的渲染更新。
主要功能:
Widget是Element的配置描述,持有公共属性和提供公开方法。Widget仅仅只持有控件的配置信息,并不会参与UI的渲染。所以即是widget会频繁的创建和销毁,也不会影响到渲染的性能。从渲染的角度进行分类,分为可渲染Widget与不可渲染Widget,只有生成Element对应为RenderObjectElement和它的子类才有RenderObject可以渲染到页面上,我们常用的StatelessWidget与StatefulWidget就属于不可渲染的WidgeElement是Widget在树中特定位置的一个实例,这个是真正的节点,用来关联Widget与渲染对象。每一个Widget都对应着一个Element,Widget实例会经常变动,但是渲染树不能经常改变,因为实例化一个RenderObject的成本是很高的,频繁的实例化和销毁RenderObject对性能的影响比较大,所以当Widget树改变的时候,Flutter使用Element树来比较新的Widget树和原来的Widget树,如果某一个位置的Widget和新Widget一致时,则只需要修改RenderObject的配置,不用进行耗费性能的RenderObject的实例化工作了 (具体看 updateChild 函数解析)Render树中的对象,主管渲染,测量自身Size并进行绘制放置子节点
这三者的关系:
根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。每一个 Widget 都会有其对应的 Element,但是只有需要渲染的Widget才会有对应的RenderObject
具体对应关系如下:
| Widget | Element | RenderObject |
|---|---|---|
| StatelessWidget | StatelessElement | - |
| StatefulWidget | StatefulElement | - |
| ProxyWidget | ProxyElement | - |
| InheritedWidget | InheritedElement | - |
| SingleChildRenderObjectWidget | SingleChildRenderObjectElement | RenderObject |
| MultiChildRenderObjectWidget | MultiChildRenderObjectElement | RenderObject |
| RenderObjectWidget | RenderObjectElement | RenderObject |
3、Flutter 页面渲染
Flutter渲染流程
对iOS的原生绘制来说,UIView 树由上到下遍历每一个 UIView,UIView进行Constraint—Layout—Display。而在 Flutter开 发中界面是由 Widget 组成的,渲染在 Framework 层会有 Build、Layout、Paint、Composite Layer 等几个阶段。
将 Layer 进行组合,生成纹理,使用 OpenGL 的接口向 GPU 提交渲染内容进行光栅化与合成,是在 Flutter 的 C++ 层,使用的是 Skia 库代替原生的 Core Graphics。
- Build: 开始构建 Widget,将 UI 配置转换成可渲染的数据结构
- Layout: 确定每个 Widget 的位置和大小
- Paint: 将 Widget 绘制成用户看到的样子,生成图层或者 Texture
- Composite: 把Paint过程生成图层或纹理按照顺序进行组合合成,以便可以高效的将 Widget 呈现到屏幕上
Flutter渲染时机
在Flutter应用中,触发渲染(树的更新)主要有以下几种时机:
- 在Futter启动时
runApp(Widget app)全局刷新 - 开发者主动调用
setState()方法: 将该子树做StatefullWidget的一个子widget,并创建对应的State类实例,通过调用state.setState() 触发该子树的刷新 - 热重载
通过上面三种方式,发通知 Flutter的framework通知状态发生改变,Framework通知Engine渲染,Engine等下一个Vsync(垂直同步信号)到来后,触发Framework开始执行渲染操作(UI线程),生成LayerTree传递给Engine,Engine的GPU线程进行合成和光栅化等操作后展示到屏幕上
build
- 在配置好
Widget后,Flutter会生成一个对应的Element,而Element又会调用Widget的方法生成一个RenderObject, 递归调用遍历子节点这样就生成了三棵树,Element同时持有Widget和RenderObject的引用 - 而需要更新的时候会从标记了
_dirty的子树开始,对比每个子节点Widget的runtimeType和key,这时会有两种情况:1、Widget的runtimeType及key的值相同,认为是同一个Widget,Flutter会复用其对应的Element和RenderObject节点,只更新RenderObject的属性值,最终从Render树中找到渲染对象并将其更新。2、runtimeTpe或key的值不同,则认为不是同一个Widget,不可复用,需要重新创建对应的Element和RenderObject
Layout
Layout的整体过程是在RenderObject中进行的,Constraints主要提供了minWidth、maxWidth、minHeight、maxHeight4个属性,用以对子节点进行约束,接收该Constraints的子节点在计算自己的大小时就有了两个条件:
- minWidth <= width <= maxWidth
- minHeight <= height <= maxHeight
在向下遍历子节点时,将自身的
Constraints传递下去,子节点再将自己的Constraints向下传递,递归触底时将计算好的Size向上传递,根据所有子节点的Size最终确定自身的Size再向上传递,递归结束则完成整个Layout过程,此时所有节点的大小已经确定了,而节点的位置则由父节点根据每个子节点的大小来确定,也就是说,子节点的位置从父节点的左上角开始。
RelayoutBoundary
当一个
RenderObject的大小被改变时,其父RenderObject的大小可能也会被影响,因此需要通知其父节点。如果这样迭代上去,需要通知整棵RenderObject Tree重新布局,必然会影响布局效率。因此,Flutter通过RelayoutBoundary将RenderObject Tree分段,如果遇到了RelayoutBoundary,则不去通知其父节点重新布局,因为其大小不会影响父节点的大小。这样做的目的是避免子控件布局的时候导致父控件和兄弟控件重新布局,只需要对RenderObject Tree中的一段重新布局提高了布局效率,RenderObject存在以下三种情况会触发 Flutter 里的RelayoutBoundary:
constraints.isTight 为true,表示约束(constraints)确定后,盒子大小就唯一确定。比如当盒子的最大高度和最小高度一样,同时最大宽度和最小宽度一样时,那么盒子大小就确定了parentUsesSize 为false,表示子节点的布局不会影响父节点,父节点不会根据子节点的大小来调整自身。父节点调用子节点 Layout 方法时传入sizedByParent 为true,表示子控件的大小只受到父节点传给子节点的约束(constraints)影响,不会受到自己子节点的影响,如该节点始终充满父节点。
Paint
经过Layout过程之后,剩下要做的就是把每个节点绘制出来,Paint 简单的说就是把 Rende Tree 转化成 Layer Tree的过程
Layer的主要作用就是为了减小绘制范围,像途中一个ListView中从左边滑动到右边的状态,如果没有Layer每滚动1像素都需要进行一次重新绘制一次,这中性能消耗是非常大的。如果使用Layer的话如图中每种颜色代表一个图层,这样一来,在滚动的时候只需要改变每个图层的偏移量,而且只需要生成有限个图层就可以实现无限长度的滚动列表,因为被移出屏幕的图层可以被重新利用,这种图层复用机制在很大程度上提升了Flutter的性能。
一个RenderObject 可能绘制在一个 Layer 上,如图中 元素1,也可能同事绘制在几个Layer上面如元素2,也有可能几个RenderObject绘制在一个Layer上如元素1 元素2 元素3,绘制Flutter进行深度优先遍历。
- 绘制第一个节点它的context可能是父对象传到RenderObject上的当前context对应的Layer是A,这样的话绘制节点1的时候就会把它绘制在LayerA上:context._layer=A
- 绘制完成节点1的时候,接下来先绘制节点2,节点只是简单的绘制(比如简单的调用 drawRect 方法)这时候就会绘制到父节点传过来 context 的layer上: context.currentLayer = A
- 绘制节点3的时候与节点2相同: context.currentLayer = A
- 绘制节点4的时候,可能调用了pushLayer方法push了一个新的Layer,这样节点4就绘制在了LaberB上面: context.pushLayer(B)
- 绘制节点5的时候与节点4相同,同样push了一个新的layer,这样节点4就绘制在了LaberC上面: context.pushLayer(C)
- 根节点左边的元素都绘制完成后再绘制右边的节点,绘制的时候还是使用当前的Layer,这样就会在了LayerC上面: context.currentLayer = C
这样一棵RenderObjectTree就完成绘制,一棵RenderObjectTree绘制在了三个不同的Layer上
RepaintBoundary
上图可以看到,如果黄色图层的内容对绿色图层有影响,那么当黄色图层重绘时,节点 5、6 都需要重绘,而实际上节点 6 与黄色节点 4 并没有相互关系,这样就会造成多余的重绘操作了,而且在这种影响下,可能节点 2 也需要被重绘,那原则上相当于整棵树都需要重绘,这与Layout过程遇到的问题是类似的,解决方案也类似,如果只想针对局部子树进行重绘,只需要在这棵子树添加一个标记
RepaintBoundary,这样一来,重绘操作就只发生在相关的子树中了,这时RepaintBoundary前后节点的目标图层都相对独立,互不影响
Composite
在Paint过程中产生了很多图层 (layers),在Composite过程中会将多个小碎片组合合成为一个新的图层,比如上面例子中的 节点 2 和节点 5
4、页面构建更新代码解析
页面创建过程
修改下生成的Demo工程,让它更简单
修改首页代码
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: Text("Test", style: TextStyle(color: Colors.blue)),
),
);
}
}
显示的页面
Demo页面最后生成的树形结构大概是这个样子的(因为只有一个元素,所以看起来像个 List ,已忽略 MaterialApp 层级)
runApp
我们编写Flutter应用入口函数都是 main 方法,其内部调用了 runApp 方法,所以我们直接看 runApp方法的实现
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
整个函数使用连级符调用了三个方法:
- WidgetsFlutterBinding 初始化(ensureInitialized)
- 根节点核心三棵树绑定穿件工作(scheduleAttachRootWidget)
- 绘制热身帧
WidgetsFlutterBinding.ensureInitialized
我们主要查看前两个函数
WidgetsFlutterBinding.ensureInitialized() 函数
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
abstract class BindingBase {
BindingBase() {
......
initInstances();
......
}
}
WidgetsFlutterBinding 继承自 BindingBase,WidgetsFlutterBinding中 with 了大量的 mixin 类。mixins 的中文意思是混入,就是在类中混入其他功能。在Dart中可以使用mixins实现类似多继承的功能,在 mixin 类中调用 super 方法会优先调用最后混入的类内的方法
通过 ensureInitialized() 方法我们可以得到一个全局单例的 WidgetsFlutterBinding 实例,且 mixin 的一堆 xxxBinding 也被实例化。 BindingBase 抽象类的构造方法中会调用 initInstances()方法,而各种 mixin 的 xxxBinding 实例化重点也都在各自的initInstances()方法中,每个 xxxBinding 的职责不同,具体如下:
WidgetsFlutterBinding:核心桥梁主体,Flutter app 全局唯一。BindingBase:绑定服务抽象类。GestureBinding:Flutter 手势事件绑定,处理屏幕事件分发及事件回调处理,其初始化方法中重点就是把事件处理回调_handlePointerDataPacket函数赋值给 window 的属性,以便 window 收到屏幕事件后调用。SchedulerBinding:Flutter 绘制调度器相关绑定类,debug 编译模式时统计绘制流程时长等操作。ServicesBinding:Flutter 系统平台消息监听绑定类。即 Platform 与 Flutter 层通信相关服务,同时注册监听了应用的生命周期回调。PaintingBinding:Flutter 绘制预热缓存等绑定类。SemanticsBinding:语义树和 Flutter 引擎之间的粘合剂绑定类。RendererBinding:渲染树和 Flutter 引擎之间的粘合剂绑定类,内部重点是持有了渲染树的根节点。WidgetsBinding:Widget 树和 Flutter 引擎之间的粘合剂绑定类。主要作用就是调度帧渲染任务、瞬间渲染、持久渲染与渲染回调任务等
WidgetsBinding.initInstances 与 RendererBinding.initInstances
主要看下渲染相关的 Binding
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
......
/**
*创建一个管理Element的类对象
*BuildOwner类用来跟踪哪些Element需要重建,并处理用于Element树的其他任务,
例如管理不活跃的Element等,调试模式触发重建等。
*/
_buildOwner = BuildOwner();
// 回调方法赋值,当第一个可构建元素被标记为脏时调用。
buildOwner!.onBuildScheduled = _handleBuildScheduled;
......
}
}
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
/**
* 创建管理rendering渲染管道的类
* 提供接口调用用来触发RenderObject布局渲染
*/
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
// 一堆window变化相关的回调监听
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
// 添加页面刷新回调函数
addPersistentFrameCallback(_handlePersistentFrameCallback);
// 创建RenderView对象,也就是RenderObject渲染树的根节点
initRenderView();
......
}
void initRenderView() {
......
// 渲染树的根节点对象
renderView = RenderView(configuration: createViewConfiguration(), window: window);
// renderView 添加到 _nodesNeedingLayout 和 _nodesNeedingPaint 列表等待渲染
renderView.prepareInitialFrame();
}
RenderView get renderView => _pipelineOwner.rootNode! as RenderView;
set renderView(RenderView value) {
assert(value != null);
_pipelineOwner.rootNode = value;
}
}
初始化过程我们已经得到了一些信息,注意两点:
RendererBinding中的RenderView就是RenderObject渲染树的根节点,代码中所有生成的RenderObject都会挂在到它的下面WidgetsBinding内部的BuildOwner类是管理Element的类对象,用来跟踪哪些Widget需要重建PipelineOwner是管理RenderObject类,提供接口调用用来触发渲染
scheduleAttachRootWidget
接下来看初始化之后调用的函数scheduleAttachRootWidget
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@protected
void scheduleAttachRootWidget(Widget rootWidget) {
// 一个耗时操作,异步运行。runApp会优先调用 scheduleWarmUpFrame() 渲染预热帧
Timer.run(() {
attachRootWidget(rootWidget);
});
}
void attachRootWidget(Widget rootWidget) {
// 检测是否是第一帧
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
// 桥梁创建RenderObject、Element、Widget关系树,
// _renderViewElement值为attachToRenderTree方法返回值
// 继承自RenderObjectToWidgetElement,把他看成是一个普通的RenderObjectElement即可
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
// 来自初始化时候RendererBinding的_pipelineOwner.rootNode
container: renderView,
debugShortDescription: '[root]',
// Demo 中的 MyApp
child: rootWidget,
// attach过程,buildOwner来自WidgetsBinding初始化时实例化的BuildOwner实例,
// renderViewElement值就是_renderViewElement自己,
// 此时由于调用完attach才赋值,所以首次进来也是null
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
// 首帧主动更新一下,内部本质是调用
// SchedulerBinding的scheduleFrame()方法。
// 进而本质调用了window.scheduleFrame()方法。
SchedulerBinding.instance!.ensureVisualUpdate();
}
}
}
内部主要调用了RenderObjectToWidgetAdapter 类,进入类的内部
createElement方法:每个widget都提供createElement方法,每个Element初始化时都必须有个widget参数,调用createElement时会把调用者传入
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
......
// 我们编写dart的runApp函数参数中传递的Flutter应用Widget树根,上一步传进来的
final Widget? child;
// 继承自RenderObject,来自PipelineOwner对象的rootNode属性,
// 一个Flutter App全局只有一个PipelineOwner实例
// 上一步传进来的
final RenderObjectWithChildMixin<T> container;
......
// 重写Widget的createElement实现,构建了一个RenderObjectToWidgetElement实例,它继承于Element。
// Element树的根结点是RenderObjectToWidgetElement。
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
// 重写Widget的createRenderObject实现,container本质是一个RenderView。
// RenderObject树的根结点是RenderView。
@override
RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
@override
void updateRenderObject(BuildContext context, RenderObject renderObject) { }
/**
* 下面代码片段中RenderObjectToWidgetAdapter实例创建后调用
* owner来自WidgetsBinding初始化时实例化的BuildOwner实例,element 值就是自己。
* 该方法创建根Element(RenderObjectToWidgetElement),
* 并将Element与Widget进行关联,即创建WidgetTree对应的ElementTree。
* 如果Element已经创建过则将根Element中关联的Widget设为新的(即_newWidget)。
* 可以看见Element只会创建一次,后面都是直接复用的。
*/
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
// 由于首次实例化RenderObjectToWidgetAdapter调用attachToRenderTree后才不为null,所以当前流程为null
if (element == null) {
// 在lockState里面代码执行过程中禁止调用setState方法
owner.lockState(() {
// 创建一个Element实例
// 构建一个RenderObjectToWidgetElement实例,
// 继承RootRenderObjectElement,又继续继承RenderObjectElement,接着继承Element。
element = createElement();
assert(element != null);
// 给根Element的owner属性赋值为WidgetsBinding初始化时实例化的BuildOwner实例。
element!.assignOwner(owner);
});
// 重点!mount里面RenderObject
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
// 更新widget树时_newWidget赋值为新的,然后element数根标记为markNeedsBuild
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
......
}
BuildOwner.buildScope (创建页面时)
在创建页面时候BuildOwner.buildScope 方法可以简单的看成只执行了 block 方法,主要看 RenderObjectToWidgetElement 的 mount 方法
class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
......
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_rebuild();
}
...
}
RenderObjectToWidgetElement的继承结构是RenderObjectToWidgetElement->RootRenderObjectElement->RenderObjectElement->Element
RenderObjectElement主要执行的代码是使用 Widget 的 createRenderObject方法生成 RenderObject并且赋值给Element
abstract class RenderObjectElement extends Element {
@override
RenderObjectWidget get widget => super.widget as RenderObjectWidget;
@override
RenderObject get renderObject => _renderObject!;
RenderObject? _renderObject;
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
......
// 通过widget树调用createRenderObject方法传入Element实例自己获取RenderObject渲染树。
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
......
}
@override
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
// 寻找可用的父RenderObject,再添加新的节点
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
}
基类Element的mount方法,仅仅是把parent记录在此element中,更新slot和depth信息
abstract class Element extends DiagnosticableTree implements BuildContext {
void mount(Element? parent, Object? newSlot) {
......
// 查找父 element ,获取父element对自己的布局约束等
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// BuildOwner 类型,从 parent 传过来,跟节点的在初始化的时候设置,
// 整个 App 只存在一个 BuildOwner
_owner = parent.owner;
}
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
}
再看 _rebuild 方法
class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
......
void _rebuild() {
try {
// updateChild同样也是界面创建与刷新时的重要处理过程,后面会详细说明,
// 这里只需要认为这里会进行子控件的添加,而且是递归添加处理,分别调用子控件的mount操作。
// 其中widget.child就是我们传入的Widget实例
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: ErrorDescription('attaching to the render tree'),
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
...
}
到这里可以简单的理解为RenderObjectToWidgetElement初始化时绑定了它与RenderObjectToWidgetAdapter既 Element与Widget,然后调用了mount方法生成RenderObject绑定了它与RenderObject的关系既 Element与Render的关系。然后递归调用了updateChild()方法,生成了整个渲染树
目前加载过程流程图如下:
updateChild(创建页面时)
abstract class Element extends DiagnosticableTree implements BuildContext {
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
......
final Element newChild;
// 第一次,Element child是null,执行else里的逻辑,
// inflateWidget使用子widget来创建一个子Element
if (child != null) {
......
} else {
newChild = inflateWidget(newWidget, newSlot);
}
......
return newChild;
}
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
// 如果 widget的Key是GlobalKey的话,会先从GlobalKey中获取引用的Element,
// 如果有lement的话就更新复用
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
// 否则就用 widget调用其createElement()来创建了一个element
// Element初始化需要Widget参数,创建完成后newChild的widget参数就是newWidget
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
// 接着就调用新建的子element的mount方法
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
}
mount方法比较复杂根据不同的 element 类型有几种分支, element是个抽象类有两个抽象子类, RenderObjectElement 和 ComponentElement,他们各自还有自己的字类,具体类如下图
具体代码调用过程如下:ComponentElement类的mount方法
abstract class ComponentElement extends Element {
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
@override
void performRebuild() {
......
Widget? built;
try {
......
// build函数为子类StatefulElement和StatelessElement的build方法
built = build();
.....
} catch (e, stack) {
.....
} finally {
......
}
......
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
......
}
......
}
}
class StatelessElement extends ComponentElement {
......
@override
Widget build() => widget.build(this);
......
}
class StatefulElement extends ComponentElement {
......
@override
Widget build() => state.build(this);
......
}
在 Flutter 里面最常见的 StatelessWidget 和 StatefulWidget 的 build 方法就是在这里被调用的。然后就又调用到了updateChild方法,这就回到了上边流程一直往下遍历创建widget树。
SingleChildRenderObjectElement类和MultiChildRenderObjectElement类的mount方法
class SingleChildRenderObjectElement extends RenderObjectElement {
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
}
class MultiChildRenderObjectElement extends RenderObjectElement {
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
for (int i = 0; i < children.length; i += 1) {
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
children[i] = newChild;
previousChild = newChild;
}
_children = children;
}
}
SingleChildRenderObjectElement类和MultiChildRenderObjectElement类都继承自RenderObjectElement,这个方法之前已经看过主要执行的代码是使用 Widget 的 createRenderObject方法生成 RenderObject并且赋值给Element,然后寻找到寻找可用的父RenderObject,再添加新的节点
通过代码可以看到 Element 调用mount()方法时
componentElement的mount方法主要作用是执行build(根据类型区分widget.build,state.build)renderObjectElement的mount方法主要作用是生成RenderObjectElement创建完成时就会调用mount, 调用顺序为 mount -> _firstBuild -> reBuild -> performRebuild -> build
updateChild总结
总结一下:updateChild是一个递归的过程,总结下来有下面几个步骤
Element如果是RenderObjectElement则创建RenderObject,并从祖先找到上一个RenderObjectElement,然后调用祖先RenderObjectElement的RenderObject的insertRenderObjectChild方法插入创建的RenderObject- 如果子
widget需要build出来就调用build方法创建子widget,如果不需要直接在成员变量可以拿到子widget - 调用子
widget的createElement创建子Element - 调用子
Element的mount方法将子Element的parent设置成自己,然后子Element去到第1步
构建流程如下:
scheduleAttachRootWidget 函数完成Flutter App 中的 Widget、Element 和 RenderObject树生成和相互关联。在函数最后调用了SchedulerBinding.instance!.ensureVisualUpdate(); 通知Engine有UI需要更新渲染页面 (后面详细描述)
总结一下runApp方法的大体过程
- 调用
runApp(Widget)函数传入一个Widget作为根Widget。Widget只是一个配置类,不是实际的UI元素。 runApp通过WidgetsFlutterBindingmixIn继承一众父类进行初始化。- 其中,
RendererBinding中的renderView对象,是实际的渲染对象。 - 通过
RenderObjectToWidgetAdapter类(继承自RenderObjectWidget我们runApp中传递的 Widget 树就被追加到了这个树根的 child 属性上)生成一个RenderObjectToWidgetElement<RenderBox>类型的Element作为根Element,并让Widget、renderView和BuildOwner和根Element产生关系,然后通过 mount 方法生成树形结构。 - 最后调用
SchedulerBinding.instance.ensureVisualUpdate()函数,等待下一帧渲染 scheduleAttachRootWidget是一个耗时操作,异步运行。runApp会优先调用scheduleWarmUpFrame()渲染预热帧。
页面构建流程如下:
更新页面
setState
在 Flutter 中我们直接通过 setState 方法来对页面进行刷新,所以直接查看源码,去掉了 assert 异常处理相关代码
abstract class State<T extends StatefulWidget> with Diagnosticable {
@protected
void setState(VoidCallback fn) {
final Object? result = fn() as dynamic;
_element!.markNeedsBuild();
}
}
直接调用 setState 传入的函数,然后调用 Element 的 markNeedsBuild 方法
abstract class Element extends DiagnosticableTree implements BuildContext {
void markNeedsBuild() {
if (_lifecycleState != _ElementLifecycle.active)
return;
.....
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
}
这里面将 Element 标记为 dirty,然后调用 BuildOwner 类的 scheduleBuildFor 方法,BuildOwner 实例在 WidgetsBinding 中初始化整个App中只有一个实例
BuildOwner.scheduleBuildFor
继续查看:
class BuildOwner {
void scheduleBuildFor(Element element) {
......
// 判断 element 是否已经加入到 _dirtyElements 列表中,
// 若是已经在列表中,就直接返回,不用再执行下面的操做
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
// 判断 _scheduledFlushDirtyElements 是否为 false ,这个变量表示当前是否正在 rebuild
// _dirtyElements 中的元素。若是没有正在 rebuild ,而且 onBuildScheduled 回调不为空
// 就调用 onBuildScheduled 函数
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
......
}
}
这里将该element加入到_dirtyElements中,标记这个节点刷新时需要进行处理。onBuildScheduled 方法在初始化中设置的具体代码为 WidgetsBinding 中 buildOwner!.onBuildScheduled = _handleBuildScheduled;具体代码如下
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
void _handleBuildScheduled() {
ensureVisualUpdate();
}
}
直接调用到SchedulerBinding类的ensureVisualUpdate方法
mixin SchedulerBinding on BindingBase {
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
ensureFrameCallbacksRegistered();
// 执行代码
// void scheduleFrame() => platformDispatcher.scheduleFrame();
// void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
// 调用 Flutter Engine 方法
window.scheduleFrame();
_hasScheduledFrame = true;
}
@protected
void ensureFrameCallbacksRegistered() {
// 调用 void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'
// 在下一个适当的机会调用 onBeginFrame 和 onDrawFrame 回调 ()
// onBeginFrame 主要进行是做一些准备工作,让framework准备好绘制工作,例如重新设置状态、变量等等
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
void _handleDrawFrame() {
......
handleDrawFrame();
}
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
// 执行 _persistentCallbacks 数组内的 callback
// _persistentCallbacks 数组在初始化 WidgetsBinding 中 addPersistentFrameCallback 方法插入
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
......
} finally {
......
_currentFrameTimeStamp = null;
}
}
}
SchedulerBinding.instance.ensureVisualUpdate
经过一系列的函数调用,调用 SchedulerBinding.instance.ensureVisualUpdate 最后会调用到WidgetsBinding中 addPersistentFrameCallback 设置的方法
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
}
WidgetsBinding.drawFrame
drawFrame函数有两个,一个在 WidgetsBinding 内一个在
RendererBinding内,优先调用WidgetsBinding内的函数
@override
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
void drawFrame() {
......
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
// 清理不再使用的 element
buildOwner!.finalizeTree();
} finally {
......
}
......
}
}
又调用到了buildOwner.buildScope方法,之前创建界面时调用了这个方法,现在刷新时也用到了,创建页面时只是简单的看成只执行了 block 方法,现在详细说明一下:
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
......
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
......
_dirtyElementsNeedsResorting = false;
try {
callback();
} finally {
......
}
}
// 首先将_dirtyElements进行排序,这是因为节点可能有很多个,
// 如果其中两个节点存在级联关系,父级的Widget build操作必然会调用到子级的Widget build,
// 如果子级又自己build一次,相当于出现了重复操作。因此通过深度排序就会避免这个问题
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
// 对每一个Element进行遍历
while (index < dirtyCount) {
......
try {
// 执行rebuild操作
_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;
}
}
}
......
return true;
}());
} finally {
// 所有Element都rebuild后,清空 _dirtyElements 集合,节点状态恢复正常
for (final Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
......
}
assert(_debugStateLockLevel >= 0);
}
Element.rebuild
Element.rebuild()方法调用了子类的 performRebuild()方法ComponentElement类页面创建过程看到过,在更新时会再次调用
@override
void performRebuild() {
......
Widget? built;
try {
......
// build函数为子类StatefulElement和StatelessElement的build方法
built = build();
.....
} catch (e, stack) {
.....
} finally {
......
_dirty = false;
}
......
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
......
}
......
}
最后还是回到 updateChild 方法,构建时只看了 child 为空的情况,现在看 child 不为空只更新的情况
abstract class Element extends DiagnosticableTree implements BuildContext {
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
// 如果不存在新的Widget,那么说明这一个节点应该取消掉了,
// 执行deactivateChild 删除节点方法。
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
// 如果子节点的widget和新的widget一致(这里的一致指的是同一个对象)
// 直接返回这个子节点。
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
// 如果两个widget不是同一个对象,判断类型是否相同,通过canUpdate方法判断
// 依据是Widget类型一致,同时Key一致
// 这种情况下,只需要更新子节点
// 因此这一步就是widget变更,但是element不变更
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
// 其它情况下则认为子节点是新增的,先删除原来的再建新的
// 调用`inflateWidget`进行子节点创建
// 里面与创建界面相同,执行了mount操作
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
......
}
......
return newChild;
}
}
element.update方法会把newWidget记录下来
abstract class Element extends DiagnosticableTree implements BuildContext {
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
}
StatelessElement.update方法会调用rebuild,rebuild中会调用performRebuild()去重建其子widget,类似一个递归的流程
class StatelessElement extends ComponentElement {
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
}
StatefulElement.update方法,先回调state.didUpdateWidget(这里就是我们在自定义Widget写的生命周期回调函数就是在这里触发的),最后又调用rebuild
@override
class StatefulElement extends ComponentElement {
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
try {
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
} finally {
}
rebuild();
}
}
SingleChildRenderObjectElement.update方法,调用 updateChild
class SingleChildRenderObjectElement extends RenderObjectElement {
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_child = updateChild(_child, widget.child, null);
}
}
MultiChildRenderObjectElement.update方法,调用 updateChildren,内部循环调用updateChild方法
class MultiChildRenderObjectElement extends RenderObjectElement {
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
}
RenderObjectElement.update方法,update方法里面只是更新widget的配置,这里会对 renderObject进行修改
abstract class RenderObjectElement extends Element {
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
}
RenderObjectElement 的 performRebuild方法
abstract class RenderObjectElement extends Element {
@override
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
}
widget.updateRenderObject每一种RenderObjectElement都会有自己的updateRenderObject处理方式,处理完成后如果需要重新计算大小宽高就会加到 PipelineOwner 的 _nodesNeedingLayout列表中,如果需要重新绘制就加到 PipelineOwner 的 _nodesNeedingPaint 列表中
剩下执行super.drawFrame() 这行代码就是调用 RendererBinding 类的 drawFrame 函数
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@protected
void drawFrame() {
assert(renderView != null);
// 调用 RenderView.performLayout(),遍历子节点,子节点在widget.updateRenderObject已经加入到列表内
// 调用每个节点的 layout(),RenderObject的排版数据,使得每个RenderObject最终都能有正确的大小和位置
pipelineOwner.flushLayout();
// 更新渲染对象,此阶段每个渲染对象都会了解其子项是否需要合成
// 在绘制阶段使用此信息选择如何实现裁剪等视觉效果
pipelineOwner.flushCompositingBits();
// 会调用 RenderView.paint() 最终触发各个节点的 paint(),最终生成一棵Layer Tree,并把绘制指令保存在Layer中
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
// 把Layer Tree提交给GPU
renderView.compositeFrame();
//
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
}
setState 总结
总结一下 setState 过程
- 首先调用
markNeedsBuild方法,将element的dirty标记为true,表示需要重建 - 接着调用
scheduleBuildFor,将当前的element添加到_dirtyElements列表中 - 调用
buildOwner.buildScope,函数内部对_dirtyElements列表中的element调用rebuild函数 rebuild函数调用updateChild循环更新子elementRenderObjectElement调用updateRenderObject,对RenderObject更新- 最后调用
pipelineOwner相关方法最后更新界面
更新流程图如下: