谈谈 Flutter 的 RunApp 与三棵树诞生流程?

开发 前端
从写 Flutter 第一行程序开始我们就知道在 Dart 的 main 方法中通过调用 runApp 方法把自己编写的 Widget 传递进去,只有这样编译运行后才能得到预期效果。

[[415500]]

背景

从写 Flutter 第一行程序开始我们就知道在 Dart 的 main 方法中通过调用 runApp 方法把自己编写的 Widget 传递进去,只有这样编译运行后才能得到预期效果。你有没有好奇这背后都经历了什么?runApp 为什么这么神秘?或者说,在你入门 Flutter 后应该经常听到或看到过 Flutter 三棵树核心机制的东西,你有真正的想过他们都是什么吗?如果都没有,那么本文就是一场解密之旅。

Flutter 程序入口

我们编写的 Flutter App 一般入口都是在 main 方法,其内部通过调用 runApp 方法将我们自己整个应用的 Widget 添加并运行,所以我们直接去看下 runApp 方法实现,如下:

/** 
 * 位置:FLUTTER_SDK\packages\flutter\lib\src\widgets\binding.dart 
 * 注意:app参数的Widget布局盒子约束constraints会被强制为填充屏幕,这是框架机制,自己想要调整可以用Align等包裹。 
 * 多次重复调用runApp将会从屏幕上移除已添加的app Widget并添加新的上去, 
 * 框架会对新的Widget树与之前的Widget树进行比较,并将任何差异应用于底层渲染树,有点类似于StatefulWidget 
调用State.setState后的重建机制。 
 */ 
void runApp(Widget app) { 
  WidgetsFlutterBinding.ensureInitialized() 
    ..scheduleAttachRootWidget(app) 
    ..scheduleWarmUpFrame(); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

可以看到上面三行代码代表了 Flutter 启动的核心三步(级联运算符调用):

  1. WidgetsFlutterBinding 初始化(ensureInitialized())
  2. 绑定根节点创建核心三棵树(scheduleAttachRootWidget(app))
  3. 绘制热身帧(scheduleWarmUpFrame())

WidgetsFlutterBinding 实例及初始化

直接看源码,如下:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { 
  static WidgetsBinding ensureInitialized() { 
    if (WidgetsBinding.instance == null
      WidgetsFlutterBinding(); 
    return WidgetsBinding.instance!; 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

WidgetsFlutterBinding 继承自 BindingBase,并且 with 了大量的 mixin 类。WidgetsFlutterBinding 就是将 Widget 架构和 Flutter Engine 连接的核心桥梁,也是整个 Flutter 的应用层核心。通过 ensureInitialized() 方法我们可以得到一个全局单例的 WidgetsFlutterBinding 实例,且 mixin 的一堆 XxxBinding 也被实例化。

BindingBase 抽象类的构造方法中会调用initInstances()方法,而各种 mixin 的 XxxBinding 实例化重点也都在各自的initInstances()方法中,每个 XxxBinding 的职责不同,如下:

  • WidgetsFlutterBinding:核心桥梁主体,Flutter app 全局唯一。
  • BindingBase:绑定服务抽象类。
  • GestureBinding:Flutter 手势事件绑定,处理屏幕事件分发及事件回调处理,其初始化方法中重点就是把事件处理回调_handlePointerDataPacket函数赋值给 window 的属性,以便 window 收到屏幕事件后调用,window 实例是 Framework 层与 Engine 层处理屏幕事件的桥梁。
  • SchedulerBinding:Flutter 绘制调度器相关绑定类,debug 编译模式时统计绘制流程时长等操作。
  • ServicesBinding:Flutter 系统平台消息监听绑定类。即 Platform 与 Flutter 层通信相关服务,同时注册监听了应用的生命周期回调。
  • PaintingBinding:Flutter 绘制预热缓存等绑定类。
  • SemanticsBinding:语义树和 Flutter 引擎之间的粘合剂绑定类。
  • RendererBinding:渲染树和 Flutter 引擎之间的粘合剂绑定类,内部重点是持有了渲染树的根节点。
  • WidgetsBinding:Widget 树和 Flutter 引擎之间的粘合剂绑定类。

从 Flutter 架构宏观抽象看,这些 XxxBinding 承担的角色大致是一个桥梁关联绑定,如下:

本文由于是启动主流程相关机制分析,所以初始化中我们需要关注的主要是 RendererBinding 和 WidgetsBinding 类的initInstances()方法,如下:

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { 
  @override 
  void initInstances() { 
    ...... 
    /** 
     *1、创建一个管理Widgets的类对象 
     *BuildOwner类用来跟踪哪些Widget需要重建,并处理用于Widget树的其他任务,例如管理不活跃的Widget等,调试模式触发重建等。 
     */ 
    _buildOwner = BuildOwner(); 
    //2、回调方法赋值,当第一个可构建元素被标记为脏时调用。 
    buildOwner!.onBuildScheduled = _handleBuildScheduled; 
    //3、回调方法赋值,当本地配置变化或者AccessibilityFeatures变化时调用。 
    window.onLocaleChanged = handleLocaleChanged; 
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; 
    ...... 
  } 

 
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { 
  @override 
  void initInstances() { 
    ...... 
    /** 
     * 4、创建管理rendering渲染管道的类 
     * 提供接口调用用来触发渲染。 
     */ 
    _pipelineOwner = PipelineOwner( 
      onNeedVisualUpdate: ensureVisualUpdate, 
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, 
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, 
    ); 
    //5、一堆window变化相关的回调监听 
    window 
      ..onMetricsChanged = handleMetricsChanged 
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged 
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged 
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged 
      ..onSemanticsAction = _handleSemanticsAction; 
    //6、创建RenderView对象,也就是RenderObject渲染树的根节点 
    initRenderView(); 
    ...... 
  } 
 
  void initRenderView() { 
    ...... 
    //RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> 
    //7、渲染树的根节点对象 
    renderView = RenderView(configuration: createViewConfiguration(), window: window); 
    renderView.prepareInitialFrame(); 
  } 
  //定义renderView的get方法,获取自_pipelineOwner.rootNode 
  RenderView get renderView => _pipelineOwner.rootNode! as RenderView; 
  //定义renderView的set方法,上面initRenderView()中实例化赋值就等于给_pipelineOwner.rootNode也进行了赋值操作。 
  set renderView(RenderView value) { 
    assert(value != null); 
    _pipelineOwner.rootNode = value; 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

到此基于初始化过程我们已经得到了一些重要信息,请记住 RendererBinding 中的 RenderView 就是 RenderObject 渲染树的根节点。上面这部分代码的时序图大致如下:

通过 scheduleAttachRootWidget 创建关联三棵核心树

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { 
  @protected 
  void scheduleAttachRootWidget(Widget rootWidget) { 
   //简单的异步快速执行,将attachRootWidget异步化 
    Timer.run(() { 
      attachRootWidget(rootWidget); 
    }); 
  } 
 
  void attachRootWidget(Widget rootWidget) { 
   //1、是不是启动帧,即看renderViewElement是否有赋值,赋值时机为步骤2 
    final bool isBootstrapFrame = renderViewElement == null
    _readyToProduceFrames = true
    //2、桥梁创建RenderObject、Element、Widget关系树,_renderViewElement值为attachToRenderTree方法返回值 
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( 
      //3、RenderObjectWithChildMixin类型,继承自RenderObject,RenderObject继承自AbstractNode。 
      //来自RendererBinding的_pipelineOwner.rootNode,_pipelineOwner来自其初始化initInstances方法实例化的PipelineOwner对象。 
      //一个Flutter App全局只有一个PipelineOwner实例。 
      container: renderView,  
      debugShortDescription: '[root]'
      //4、我们平时写的dart Widget app 
      child: rootWidget, 
    //5、attach过程,buildOwner来自WidgetsBinding初始化时实例化的BuildOwner实例,renderViewElement值就是_renderViewElement自己,此时由于调用完appach才赋值,所以首次进来也是null。 
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?); 
    if (isBootstrapFrame) { 
      //6、首帧主动更新一下,匹配条件的情况下内部本质是调用SchedulerBinding的scheduleFrame()方法。 
      //进而本质调用了window.scheduleFrame()方法。 
      SchedulerBinding.instance!.ensureVisualUpdate(); 
    } 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

上面代码片段的步骤 2 和步骤 5 需要配合 RenderObjectToWidgetAdapter 类片段查看,如下:

//1、RenderObjectToWidgetAdapter继承自RenderObjectWidget,RenderObjectWidget继承自Widget 
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { 
  ...... 
  //3、我们编写dart的runApp函数参数中传递的Flutter应用Widget树根 
  final Widget? child; 
  //4、继承自RenderObject,来自PipelineOwner对象的rootNode属性,一个Flutter App全局只有一个PipelineOwner实例。 
  final RenderObjectWithChildMixin<T> container; 
  ...... 
  //5、重写Widget的createElement实现,构建了一个RenderObjectToWidgetElement实例,它继承于Element。         
  //Element树的根结点是RenderObjectToWidgetElement。 
  @override 
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this); 
  //6、重写Widget的createRenderObject实现,container本质是一个RenderView。 
  //RenderObject树的根结点是RenderView。 
  @override 
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container; 
 
  @override 
  void updateRenderObject(BuildContext context, RenderObject renderObject) { } 
 
  /** 
   *7、上面代码片段中RenderObjectToWidgetAdapter实例创建后调用 
   *owner来自WidgetsBinding初始化时实例化的BuildOwner实例,element 值就是自己。 
   *该方法创建根Element(RenderObjectToWidgetElement),并将Element与Widget进行关联,即创建WidgetTree对应的ElementTree。 
   *如果Element已经创建过则将根Element中关联的Widget设为新的(即_newWidget)。 
   *可以看见Element只会创建一次,后面都是直接复用的。 
   */ 
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) { 
    //8、由于首次实例化RenderObjectToWidgetAdapter调用attachToRenderTree后才不为null,所以当前流程为null 
    if (element == null) { 
      //9、在lockState里面代码执行过程中禁止调用setState方法 
      owner.lockState(() { 
        //10、创建一个Element实例,即调用本段代码片段中步骤5的方法。 
        //调用RenderObjectToWidgetAdapter的createElement方法构建了一个RenderObjectToWidgetElement实例,继承RootRenderObjectElement,又继续继承RenderObjectElement,接着继承Element。 
        element = createElement(); 
        assert(element != null); 
        //11、给根Element的owner属性赋值为WidgetsBinding初始化时实例化的BuildOwner实例。 
        element!.assignOwner(owner); 
      }); 
      //12、重点!mount里面RenderObject  
      owner.buildScope(element!, () { 
        element!.mount(nullnull); 
      }); 
    } else { 
      //13、更新widget树时_newWidget赋值为新的,然后element数根标记为markNeedsBuild 
      element._newWidget = this; 
      element.markNeedsBuild(); 
    } 
    return element!; 
  } 
  ...... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.

对于上面步骤 12 我们先进去简单看下 Element (RenderObjectToWidgetElement extends RootRenderObjectElement extends RenderObjectElement extends Element)的 mount 方法,重点关注的是父类 RenderObjectElement 中的 mount 方法,如下:

abstract class RenderObjectElement extends Element { 
  //1、Element树通过构造方法RenderObjectToWidgetElement持有了Widget树实例。(RenderObjectToWidgetAdapter)。 
  @override 
  RenderObjectWidget get widget => super.widget as RenderObjectWidget; 
 
  //2、Element树通过mount后持有了RenderObject渲染树实例。 
  @override 
  RenderObject get renderObject => _renderObject!; 
  RenderObject? _renderObject; 
 
  @override 
  void mount(Element? parent, Object? newSlot) { 
    ...... 
    //3、通过widget树(即RenderObjectToWidgetAdapter)调用createRenderObject方法传入Element实例自己获取RenderObject渲染树。 
    //RenderObjectToWidgetAdapter.createRenderObject(this)返回的是RenderObjectToWidgetAdapter的container成员,也就是上面分析的RenderView渲染树根节点。 
    _renderObject = widget.createRenderObject(this); 
    ...... 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

到这里对于 Flutter 的灵魂“三棵树”来说也能得出如下结论:

  • Widget 树的根结点是 RenderObjectToWidgetAdapter(继承自 RenderObjectWidget extends Widget),我们 runApp 中传递的 Widget 树就被追加到了这个树根的 child 属性上。
  • Element 树的根结点是 RenderObjectToWidgetElement(继承自 RootRenderObjectElement extends RenderObjectElement extends Element),通过调用 RenderObjectToWidgetAdapter 的 createElement 方法创建,创建 RenderObjectToWidgetElement 的时候把 RenderObjectToWidgetAdapter 通过构造参数传递进去,所以 Element 的 _widget 属性值为 RenderObjectToWidgetAdapter 实例,也就是说 Element 树中 _widget 属性持有了 Widget 树实例。RenderObjectToWidgetAdapter 。
  • RenderObject 树的根结点是 RenderView(RenderView extends RenderObject with RenderObjectWithChildMixin),在 Element 进行 mount 时通过调用 Widget 树(RenderObjectToWidgetAdapter)的createRenderObject方法获取 RenderObjectToWidgetAdapter 构造实例化时传入的 RenderView 渲染树根节点。

上面代码流程对应的时序图大致如下:

结合上一小结可以很容易看出来三棵树的创建时机(时序图中紫红色节点),也可以很容易看出来 Element 是 Widget 和 RenderObject 之前的一个“桥梁”,其内部持有了两者树根,抽象表示如下:

由于篇幅和本文主题原因,我们重心关注三棵树的诞生流程,对于三棵树之间如何配合进行绘制渲染这里先不展开,后面会专门一篇分析。

热身帧绘制

到此让我们先将目光再回到一开始runApp方法的实现中,我们还差整个方法实现中的最后一个scheduleWarmUpFrame()调用,如下:

mixin SchedulerBinding on BindingBase { 
  void scheduleWarmUpFrame() { 
    ...... 
    Timer.run(() { 
      assert(_warmUpFrame); 
      handleBeginFrame(null); 
    }); 
    Timer.run(() { 
      assert(_warmUpFrame); 
      handleDrawFrame(); 
      //重置时间戳,避免热重载情况从热身帧到热重载帧的时间差,导致隐式动画的跳帧情况。 
      resetEpoch(); 
      ...... 
      if (hadScheduledFrame) 
        scheduleFrame(); 
    }); 
 //在此次绘制结束前该方法会锁定事件分发,可保证绘制过程中不会再触发新重绘。 
 //也就是说在本次绘制结束前不会响应各种事件。 
    lockEvents(() async { 
      await endOfFrame; 
      Timeline.finishSync(); 
    }); 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

这段代码的本质这里先不详细展开,因为本质就是渲染帧的提交与触发相关,我们后边文章会详细分析 framework 层绘制渲染相关逻辑,那时再展开。在这里只用知道它被调用后会立即执行一次绘制(不用等待 VSYNC 信号到来)。

这时候细心的话,你可能会有疑问,前面分析 attachRootWidget 方法调用时,它的最后一行发现是启动帧则会调用window.scheduleFrame()然后等系统 VSYNC 信号到来触发绘制,既然 VSYNC 信号到来时会触发绘制,这个主动热身帧岂不是可以不要?

是的,不要也是没问题的,只是体验不是很好,会导致初始化卡帧的效果。因为前面window.scheduleFrame()发起的绘制请求是在收到系统 VSYNC 信号后才真正执行,而 Flutter app 初始化时为了尽快呈现 UI 而没有等待系统 VSYNC 信号到来就主动发起一针绘制(也被形象的叫做热身帧),这样最长可以减少一个 VSYNC 等待时间。

总结

上面就是 Flutter Dart 端三棵树的诞生流程,关于三棵树是如何互相工作的,我们会在后面专门篇章做分析,这里就先不展开了。

本文转载自微信公众号「码农每日一题」,可以通过以下二维码关注。转载本文请联系码农每日一题公众号。

 

责任编辑:武晓燕 来源: 码农每日一题
相关推荐

2010-07-14 11:00:51

2017-06-26 11:37:40

互联网

2021-01-12 08:20:51

AndroidActivity系统

2023-01-04 15:24:46

ACE组件UI布局

2019-11-27 11:10:58

TomcatOverviewAcceptor

2017-11-21 13:00:20

机器学习决策树可视化

2011-08-01 13:51:31

Web

2018-02-25 07:23:23

2012-07-16 10:19:02

MongoDB

2017-11-09 10:28:45

软件定义网络

2022-10-09 15:18:31

SwaggerOpenAPI工具

2021-01-19 05:46:00

算法javascript函数

2021-09-06 10:38:50

二叉搜索树递归

2021-06-04 07:55:05

MySQLB+ 树数据

2017-08-22 16:25:14

CSSHTML选择器

2021-12-08 15:10:45

鸿蒙HarmonyOS应用

2015-06-15 18:44:15

Apple Watch微游戏

2021-01-14 18:17:33

SpringFrameIOCJava

2009-07-08 16:00:57

J2SE 1.2Java2

2020-07-01 07:44:06

javaSE==equals
点赞
收藏

51CTO技术栈公众号