6

Flutter 的 runApp 与三棵树诞生流程源码分析

 2 years ago
source link: https://segmentfault.com/a/1190000040555850
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Flutter 系列文章连载~

从写 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();
}

可以看到上面三行代码代表了 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!;
  }
}

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

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

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

WidgetsFlutterBinding 实例化单例初始化之后先调用了scheduleAttachRootWidget(app)方法,这个方法位于 mixin 的 WidgetsBinding 类中,本质是异步执行了attachRootWidget(rootWidget)方法,这个方法完成了 Flutter Widget 到 Element 到 RenderObject 的整个关联过程。源码如下:

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

上面代码片段的步骤 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(null, null);
      });
    } else {
      //13、更新widget树时_newWidget赋值为新的,然后element数根标记为markNeedsBuild
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }
  ......
}

对于上面步骤 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);
    ......
  }
}

到这里对于 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<RenderBox>),在 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();
    });
  }
}

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK