1、UI 编程的两种办法

现在主流的UI编写办法首要有两种,命令式UI和声明式UI。在iOS开发中运用的UI编程办法是命令式UI(Imperative UI),而声明式UI的意思便是让开发者描绘需求一个什么样的界面。在Flutter中便是采用了声明式UI(Declarative UI)

下面这样一个简略的比方:

Flutter 页面构建渲染更新流程解析

在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.

首要包括以下几个信息

  1. Widget是指一部分 UI 的描绘
  2. Widget是不可变的
  3. Widget是对Element装备的描绘,而Element办理着底层的烘托树

榜首个信息清楚明了,各个Widget会依据咱们所写的特点 (在 Flutter 里指状况 State) 展现在屏幕上。那么第二个信息,假如Widget是不可变的,而且用的是声明式 UI,那么跟着用户的操作或许数据的改动,UI 是怎么更新的

Flutter 烘托树

简介

Flutter 页面构建渲染更新流程解析
在Flutter中,除了Widget树,还有Element树和Render树,这三棵树各司其职,完结了Flutter的烘托更新。

首要功能:

  • WidgetElement的装备描绘,持有公共特点和供给揭露办法。Widget仅仅只持有控件的装备信息,并不会参与UI的烘托。所以便是widget会频频的创立和毁掉,也不会影响到烘托的性能。从烘托的视点进行分类,分为可烘托Widget与不可烘托Widget,只要生成Element对应为RenderObjectElement和它的子类才有RenderObject能够烘托到页面上,咱们常用的 StatelessWidgetStatefulWidget 就归于不可烘托的Widge
  • ElementWidget在树中特定方位的一个实例,这个是真正的节点,用来关联Widget与烘托目标。每一个Widget都对应着一个ElementWidget实例会常常变动,可是烘托树不能常常改动,因为实例化一个RenderObject的成本是很高的,频频的实例化和毁掉RenderObject对性能的影响比较大,所以当Widget树改动的时分,Flutter运用Element树来比较新的Widget树和原来的Widget树,假如某一个方位的Widget和新Widget共一起,则只需求修正RenderObject的装备,不用进行耗费性能的RenderObject的实例化作业了 (具体看 updateChild 函数解析)
  • Render树中的目标,主管烘托,测量本身Size并进行制作放置子节点

这三者的联系:

依据Widget生成Element,然后创立相应的RenderObject并关联到Element.renderObject特点上,终究再经过RenderObject来完结布局排列和制作。每一个 Widget 都会有其对应的 Element,可是只要需求烘托的Widget才会有对应的RenderObject

Flutter 页面构建渲染更新流程解析

具体对应联系如下:

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 树由上到下遍历每一个 UIViewUIView进行ConstraintLayoutDisplay。而在 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 页面构建渲染更新流程解析
经过上面三种办法,发告诉 Flutter的framework告诉状况发生改动,Framework告诉Engine烘托,Engine等下一个Vsync(垂直同步信号)到来后,触发Framework开端履行烘托操作(UI线程),生成LayerTree传递给Engine,Engine的GPU线程进行组成和光栅化等操作后展现到屏幕上

build

  • 在装备好Widget后,Flutter会生成一个对应的Element,而Element又会调用Widget的办法生成一个RenderObject递归调用遍历子节点这样就生成了三棵树,Element一起持有Widget和RenderObject的引证
  • 而需求更新的时分会从符号了_dirty的子树开端,比照每个子节点WidgetruntimeTypekey,这时会有两种状况:1、WidgetruntimeTypekey 的值相同,认为是同一个Widget,Flutter会复用其对应的ElementRenderObject节点,只更新RenderObject的特点值,终究从Render树中找到烘托目标并将其更新。2、runtimeTpekey的值不同,则认为不是同一个Widget,不可复用,需求从头创立对应的ElementRenderObject

Layout

Flutter 页面构建渲染更新流程解析

Layout的整体进程是在RenderObject中进行的,Constraints首要供给了minWidthmaxWidthminHeightmaxHeight4个特点,用以对子节点进行束缚,接收该Constraints的子节点在计算自己的巨细时就有了两个条件:

  • minWidth <= width <= maxWidth
  • minHeight <= height <= maxHeight 在向下遍历子节点时,将本身的Constraints传递下去,子节点再将自己的Constraints向下传递,递归触底时将计算好的Size向上传递,依据一切子节点的Size终究确认本身的Size再向上传递,递归结束则完结整个Layout进程,此刻一切节点的巨细现已确认了,而节点的方位则由父节点依据每个子节点的巨细来确认,也便是说,子节点的方位从父节点的左上角开端。

RelayoutBoundary

Flutter 页面构建渲染更新流程解析
当一个RenderObject的巨细被改动时,其父RenderObject的巨细或许也会被影响,因而需求告诉其父节点。假如这样迭代上去,需求告诉整棵RenderObject Tree从头布局,必然会影响布局功率。因而,Flutter经过RelayoutBoundaryRenderObject Tree分段,假如遇到了RelayoutBoundary,则不去告诉其父节点从头布局,因为其巨细不会影响父节点的巨细。这样做的目的是防止子控件布局的时分导致父控件和兄弟控件从头布局,只需求对RenderObject Tree中的一段从头布局提高了布局功率,RenderObject存在以下三种状况会触发 Flutter 里的RelayoutBoundary:

  • constraints.isTight 为true,表明束缚(constraints)确认后,盒子巨细就仅有确认。比方当盒子的最大高度和最小高度相同,一起最大宽度和最小宽度相一起,那么盒子巨细就确认了
  • parentUsesSize 为false,表明子节点的布局不会影响父节点,父节点不会依据子节点的巨细来调整本身。父节点调用子节点 Layout 办法时传入
  • sizedByParent 为true,表明子控件的巨细只遭到父节点传给子节点的束缚(constraints)影响,不会遭到自己子节点的影响,如该节点一直充满父节点。

Paint

经过Layout进程之后,剩下要做的便是把每个节点制作出来,Paint 简略的说便是把 Rende Tree 转化成 Layer Tree的进程

Flutter 页面构建渲染更新流程解析
Layer的首要效果便是为了减小制作规模,像途中一个ListView中从左面滑动到右边的状况,假如没有Layer每翻滚1像素都需求进行一次从头制作一次,这中性能消耗是非常大的。假如运用Layer的话如图中每种颜色代表一个图层,这样一来,在翻滚的时分只需求改动每个图层的偏移量,而且只需求生成有限个图层就能够完结无限长度的翻滚列表,因为被移出屏幕的图层能够被从头利用,这种图层复用机制在很大程度上提升了Flutter的性能。

Flutter 页面构建渲染更新流程解析
一个RenderObject 或许制作在一个 Layer 上,如图中 元素1,也或许同事制作在几个Layer上面如元素2,也有或许几个RenderObject制作在一个Layer上如元素1 元素2 元素3,制作Flutter进行深度优先遍历

  1. 制作榜首个节点它的context或许是父目标传到RenderObject上的当时context对应的Layer是A,这样的话制作节点1的时分就会把它制作在LayerA上:context._layer=A
  2. 制作完结节点1的时分,接下来先制作节点2,节点仅仅简略的制作(比方简略的调用 drawRect 办法)这时分就会制作到父节点传过来 context 的layer上: context.currentLayer = A
  3. 制作节点3的时分与节点2相同: context.currentLayer = A
  4. 制作节点4的时分,或许调用了pushLayer办法push了一个新的Layer,这样节点4就制作在了LaberB上面: context.pushLayer(B)
  5. 制作节点5的时分与节点4相同,同样push了一个新的layer,这样节点4就制作在了LaberC上面: context.pushLayer(C)
  6. 根节点左面的元素都制作完结后再制作右边的节点,制作的时分仍是运用当时的Layer,这样就会在了LayerC上面: context.currentLayer = C

这样一棵RenderObjectTree就完结制作,一棵RenderObjectTree制作在了三个不同的Layer上

RepaintBoundary

Flutter 页面构建渲染更新流程解析
上图能够看到,假如黄色图层的内容对绿色图层有影响,那么当黄色图层重绘时,节点 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)),
      ),
    );
  }
}

显示的页面

Flutter 页面构建渲染更新流程解析

Demo页面终究生成的树形结构大概是这个姿态的(因为只要一个元素,所以看起来像个 List ,已忽略 MaterialApp 层级)

Flutter 页面构建渲染更新流程解析

runApp

咱们编写Flutter运用进口函数都是 main 办法,其内部调用了 runApp 办法,所以咱们直接看 runApp办法的完结

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

整个函数运用连级符调用了三个办法:

  1. WidgetsFlutterBinding 初始化(ensureInitialized)
  2. 根节点中心三棵树绑定穿件作业(scheduleAttachRootWidget)
  3. 制作热身帧

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 承继自 BindingBaseWidgetsFlutterBinding中 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;
  }
}

初始化进程咱们现已得到了一些信息,留意两点:

  1. RendererBinding中的RenderView便是RenderObject烘托树的根节点,代码中一切生成的 RenderObject 都会挂在到它的下面
  2. WidgetsBinding内部的BuildOwner类是办理Element的类目标,用来盯梢哪些Widget需求重建
  3. 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 办法,首要看 RenderObjectToWidgetElementmount 办法

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首要履行的代码是运用 WidgetcreateRenderObject办法生成 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);
  }
}

基类Elementmount办法,仅仅是把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初始化时绑定了它与RenderObjectToWidgetAdapterElementWidget,然后调用了mount办法生成RenderObject绑定了它与RenderObject的联系既 ElementRender的联系。然后递归调用了updateChild()办法,生成了整个烘托树

现在加载进程流程图如下:

Flutter 页面构建渲染更新流程解析

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是个抽象类有两个抽象子类, RenderObjectElementComponentElement,他们各自还有自己的字类,具体类如下图

Flutter 页面构建渲染更新流程解析

具体代码调用进程如下: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 里边最常见的 StatelessWidgetStatefulWidgetbuild 办法便是在这儿被调用的。然后就又调用到了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,这个办法之前现已看过首要履行的代码是运用WidgetcreateRenderObject办法生成RenderObject而且赋值给Element,然后寻觅到寻觅可用的父RenderObject,再增加新的节点

经过代码能够看到 Element 调用mount()办法时

  • componentElement的mount办法首要效果是履行build(依据类型区分widget.build, state.build
  • renderObjectElement 的mount办法首要效果是生成RenderObject
  • Element创立完结时就会调用 mount, 调用次序为 mount -> _firstBuild -> reBuild -> performRebuild -> build

updateChild总结

总结一下:updateChild是一个递归的进程,总结下来有下面几个步骤

  1. Element假如是RenderObjectElement则创立RenderObject,并从先人找到上一个RenderObjectElement,然后调用先人RenderObjectElementRenderObjectinsertRenderObjectChild办法刺进创立的RenderObject
  2. 假如子widget需求build出来就调用build办法创立子widget,假如不需求直接在成员变量能够拿到子widget
  3. 调用子widgetcreateElement创立子Element
  4. 调用子Elementmount办法将子Elementparent设置成自己,然后子Element去到第1步

构建流程如下:

Flutter 页面构建渲染更新流程解析

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()烘托预热帧。

页面构建流程如下:

Flutter 页面构建渲染更新流程解析

更新页面

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 传入的函数,然后调用 ElementmarkNeedsBuild 办法

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 办法在初始化中设置的具体代码为 WidgetsBindingbuildOwner!.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 终究会调用到WidgetsBindingaddPersistentFrameCallback 设置的办法

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;
  }
}

RenderObjectElementperformRebuild办法

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 进程

  1. 首先调用 markNeedsBuild 办法,将 elementdirty 符号为 true,表明需求重建
  2. 接着调用 scheduleBuildFor ,将当时的 element 增加到 _dirtyElements 列表中
  3. 调用buildOwner.buildScope,函数内部对 _dirtyElements 列表中的 element 调用 rebuild 函数
  4. rebuild 函数调用 updateChild 循环更新子 element
  5. RenderObjectElement 调用 updateRenderObject ,对 RenderObject 更新
  6. 终究调用 pipelineOwner相关办法终究更新界面

更新流程图如下:

Flutter 页面构建渲染更新流程解析