Flutter 源码阅读 - 三棵树流程分析(二)

634 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

Flutter 源码阅读 - 三棵树流程分析(一)一文中对 WidgetElementRenderObject 它们三个做了个简单的分类,本篇文章中,笔者将会通过一些小的案例,进行源码调试并且逐一分析,一步步揭开它们神秘的面纱。


一、前言

由于 Widget 分类可以分为组合型 WidgetRenderObjectWidget两大类以及子类别,它们分别对应不同的 Element 分类,所以文章将会按照 Widget 的分类用几个小的案例来进行分析。


二、RenderObjectWidget 案例

下面我们先通过一个简单的案例来进行分析,在 runApp 中直接渲染一个 ColoredBox,代码如下:

void main() {
  runApp(
    const ColoredBox(color: Colors.blue),
  );
}

程序运行起来后,我们看到如下的效果:

image.png

此时我们从程序入口 runApp 开始分析,源码如下:

// flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

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

它接收一个 Widget 类型的参数,接下来执行WidgetsFlutterBinding.ensureInitialized() 方法,方法体内首先会判断WidgetsBinding._instance是否为 null,若是,则会调用 WidgetsFlutterBinding 构造函数来进行初始化,最后返回 instance 实例。此时我们意外的发现 WidgetsFlutterBinding根本就没有构造函数,那么此时就会执行父类的构造函数,下面我们看下 BindingBase 的源码。

image.png

可以看到构造函数中有个 initInstances 方法,此时我们打开断点调试跟踪一下。

image.png

通过断点调试以及发现,它的调用堆栈如下图:

image.png

ensureInitialized 方法中,WidgetsBinding._instancenull,然后执行 WidgetsFlutterBinding 构造方法,此时它并没有构造方法,便会执行 BindingBase 构造函数,然后执行 initInstances 方法,大家在这里要注意下,由于 GestureBindingSchedulerBindingServicesBindingPaintingBindingSemanticsBindingRendererBindingWidgetsBinding 这七大 binding 都是混入了 BindingBase,而且每个 binding 都重写了 initInstances ,并且在内部调用了 super.initInstances(),所以它们七大 bingding 中 initInstances 的执行顺序为:WidgetsBinding -> RendererBinding -> SemanticsBinding -> PaintingBinding -> ServicesBinding -> SchedulerBinding -> GestureBinding,最后执行 BindingBase 中的 initInstances 函数,在执行的过程中把当前 this 赋值给 _instance 保存。

这里或许大家会问,initInstances 执行顺序为什么是这样的?其实是 dart 中 mixin 之线性化了。由于七大 binding 都会混入 BindingBase,而且 WidgetsFlutterBinding 继承了 BindingBase 并且依次 with 了这七大 binding。那么这里就产生了一个问题,如果with后的多个类中有相同的方法,那么当调用该方法时,会调用哪个类里的方法?由于距离with关键字越远的类会重写前面类中的相同方法,因此分为以下两种情况:

  1. 如果当前使用类重写了该方法,就会调用当前类中的方法。
  2. 如果当前使用类没有重写了该方法,则会调用距离with关键字最远类中的方法

上面已经提到了每个 binding 都重写了 initInstances ,并且在内部调用了 super.initInstances(),所以执行顺序就是这样的了。

下面我们看个小例子:

abstract class BindingBase {
  BindingBase() {
    initInstances();
  }

  void initInstances() {
    print("BindingBase-->initInstances");
  }
}

mixin GestureBinding on BindingBase {
  @override
  void initInstances() {
    print("GestureBinding-->initInstances");
    super.initInstances();
  }
}

mixin RendererBinding on BindingBase {
  @override
  void initInstances() {
    print("RendererBinding-->initInstances");
    super.initInstances();
  }
}

mixin WidgetsBinding on BindingBase {
  @override
  void initInstances() {
    print("WidgetsBinding-->initInstances");
    print('super.runtimeType--> ${super.runtimeType}');
    super.initInstances();
  }
}

mixin PaintingBinding on BindingBase {
  @override
  void initInstances() {
    print("PaintingBinding-->initInstances");
    super.initInstances();
  }
}

class WidgetsFlutterBinding extends BindingBase
    with GestureBinding, PaintingBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    return WidgetsFlutterBinding();
  }
}

main(List<String> arguments) {
  WidgetsFlutterBinding();
}

输出结果如下:

image.png

我们在 WidgetsBindingRendererBindingPaintingBindingGestureBinding中都调用了 super.initInstances 方法,也因此会一级一级往上调用。如果我们删除 WidgetsBinding 中的 super.initInstances 方法,则会终止调用 initInstances

mixin WidgetsBinding on BindingBase {
  @override
  void initInstances() {
    print("WidgetsBinding-->initInstances");
    print('super.runtimeType --> ${super.runtimeType}');
    // super.initInstances(); // 注释掉此句代码
  }
}

输出结果如下:

image.png

从上面改的分析我们可以得出结论:ensureInitialized 只是实例化了 WidgetsFlutterBinding。 此时会继续执行 scheduleAttachRootWidget 方法 ,该方法接收一个 Widget,然后继续执行 attachRootWidget,下面我们来看下 attachRootWidget 方法,做了什么事?

Takes a widget and attaches it to the [renderViewElement], creating it if necessary. This is called by [runApp] to configure the widget tree.

通过官方注释可以看出,如果有必要的话,获取一个小部件并将其附加到 renderViewElement 上。

image.png

下面着重分析下红框中的代码,首先执行 RenderObjectToWidgetAdapter 构造方法,它需要三个入参,debugShortDescription 是调试用的,这里就不做解释,child 就是我们在 runApp 中所写的 ColoredBox

image.png

container 又是什么呢,从断点中可以看出它是 RenderView?它其实就是我们刚才所讲的 RendererBindinginitInstances 所初始化的,这里首先初始化了 PipelineOwner ,然后执行 initRenderViewrenderView 进行赋值,然后通过 getter 获取到 renderView。

image.png

image.png

image.png

到此我们已经完全明白了renderViewrootWidget的来历,下面我们继续分析 attachToRenderTree 方法,它接收两个参数 BuildOwner 和可选参数 RenderObjectToWidgetElement<T>?,此时的 buildOwner 就是在 WidgetsBindinginitInstances 进行初始化的,renderViewElement 此时为 null

image.png

我们来一起看一下 attachToRenderTree 的源码,重点分析一下红框里部分的代码,如下图所示:

image.png

此时,elementnull,所以会走红框里的逻辑,首先我们来看下 owner.lockState,它接受一个 callback,其他的逻辑就是进行一些断言判断,最后执行 callback 方法,也就是会执行 element = createElement(),element!.assignOwner(owner) 这两个方法。

image.png

我们回过头来看下 createElement,接收一个 this, 也就是 RenderObjectToWidgetAdapter 这个 Widget,然后执行 RenderObjectToWidgetElement 构造方法,接着执行 super 函数(也就是 RootRenderObjectElement 构造函数),把 widget 一路传递下去,以此类推执行 RenderObjectElement 构造方法,最后执行 Element 构造方法。在 Element 构造方法中 把 RenderObjectToWidgetAdapter 赋值给 _widget,可以看出 Element 便持有了 Widget,也就是 attachToRenderTree 的返回值,这里可以得知根组件 createElement 的触发时机。

image.png

image.png

image.png

image.png

image.png

接着会执行 assignOwner 方法,这里我们先不做讲解。接着来看 owner.buildScope ,通过源码可以看出在执行 callback 之前也都是进行一些断言处理,最后会执行 callback,也就是 buildScope 中传递的回调方法。

image.png

因为此时的 elementRenderObjectToWidgetElement,所以会执行 RenderObjectToWidgetElementmount 方法。

image.png

接着会执行一系列父级的 super.mount() 方法,一直到执行 Elementmount 方法,在它的方法中主要对一些属性的赋值操作,当 Elementmount 方法 执行完后便会出栈,接着执行 RenderObjectElementmount 方法,此时会执行 widgetcreateRenderObject 方法,通过断点调试我们可以看到 widget 就是根组件 RenderObjectToWidgetAdapter

image.png

接下来执行 RenderObjectToWidgetAdaptercreateRenderObject 方法,会返回 Renderview,也就是在调用 attachRootWidget 方法中 RenderObjectToWidgetAdapter 构造函数所传递进来的 renderView

image.png

此时返回值赋值给 _renderObject ,我们可以得出,RenderObjectElement 会持有 RenderObject 对象。

接下来会执行 RenderObjectElement 中的 attachRenderObject 方法,通过源码可以看出,此方法做了三件事:

image.png

  1. 通过 _findAncestorRenderObjectElement 方法,寻找首位 RenderObjectElement 类型的节点,并且赋值给 _ancestorRenderObjectElement
  2. 调用 _ancestorRenderObjectElementinsertRenderObjectChild 方法将 renderObject 插入渲染树中;
  3. 通过 _findAncestorParentDataElement 方法找到首位 ParentDataElement<ParentData>类型的节点。

由于此时 ancestornull_findAncestorRenderObjectElement 相当于执行了个寂寞,此时的 _ancestorRenderObjectElementnull_ancestorRenderObjectElement 也不会执行,_findAncestorParentDataElement 也相当于执行了个寂寞,整体下来,attachRenderObject 相当于没有执行。

image.png

此时会继续出栈,然后继续执行 RenderObjectToWidgetElement 中的 mount 方法,执行根组件中的 _rebuild 方法,在 rebuild 方法中,主要会执行 updateChild 方法,第一个参数 _childnull,第二个参数就是我们在 runApp 中传入的 ColoredBox,第三个参数为 Object

image.png

image.png

紧接着会执行 Element 中的 updateChild 方法,因为此时 的 childnull,所以会执行 inflateWidget 方法。

image.png

从断点中可以看出传入 inflateWidget 中的 newWidgetColoredBox,我们来看下 inflateWidget 做了哪些事情:

  1. 校验 key 是否为 GlobalKey
  2. newWidget 执行 createElement
  3. 执行 newWidget 创建 createElement 返回值的 mount 方法。

看到这里是否有似曾相识的感觉,然后会走入下一个循环,执行 createElement,执行 Elementmount 方法,执行 createRenderObject 方法。

image.png

在第二件事情中会执行 createElement 方法,此时的 thisColoredBox,因为 ColoredBox 继承了 SingleChildRenderObjectWidget,所以此时会执行它里面的 createElement 方法创建一个 SingleChildRenderObjectElement,然后继续执行 super 方法,一直到 Element 构造方法执行完毕。

image.png

第三件事情中执行 SingleChildRenderObjectElement 中的 mount 方法,然后再执行 Element 中的 updateChild 方法。

image.png


三、总结

至此,我们已经走完了 ColoredBox (RenderObjectWidget) 的流程,其中有 mount 的执行时机、Element 的创建以及 RenderObject 的创建等等。由于篇幅的限制,在后续系列文章中将会讲解 StatelessWidget 案例以及 StatefulWidget 案例。