likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 72

一、前言

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


二、RenderObjectWidget 案例

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

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

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

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

此时我们从程序入口 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 的源码。

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

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

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

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

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

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

输出结果如下:

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

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

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

输出结果如下:

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

从上面改的分析我们可以得出结论: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 上。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


三、总结

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

转载自:https://juejin.cn/post/7171371644735193102
评论
请登录