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
就归于不可烘托的Widge
-
Element
是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
、maxHeight
4个特点,用以对子节点进行束缚,接收该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办法首要效果是生成RenderObject -
Element
创立完结时就会调用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
经过WidgetsFlutterBinding
mixIn承继一众父类进行初始化。 - 其间,
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
循环更新子element
-
RenderObjectElement
调用updateRenderObject
,对RenderObject
更新 - 终究调用
pipelineOwner
相关办法终究更新界面
更新流程图如下: