注:本文代码基于Flutter SDK 3.13.5

一、前言

在上一文Flutter源码之三颗树分类中,我们对Flutter的三颗树WidgetElementRenderObject进行了简单的分类,知道了Widget可分为RenderObjectWidget一类和非RenderObjectWidget另一类,并且XXXWidget基本上都有与之对应的XXXElement

在本文中,将会通过一个小案例以及Debug调试源码的方式深入分析runApp方法的执行过程。

二、小案例之设计问题

笔者在最初设计小案例时,预期想要的Widget层次结构是下图这样的。

对应的代码如下。

 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
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MyPage();
  }
}

class MyPage extends StatefulWidget {
  const MyPage({super.key});

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  @override
  Widget build(BuildContext context) {
    return const ColoredBox(color: Colors.pinkAccent);
  }
}

程序运行起来后,可以看到如下效果。

但是当笔者通过Flutter Inspector查看Widget层次结构时,发现事与愿违,它的结构变为如下所示。

可以发现,此时的Widget层次结构多出了一个View结构,这无疑会给后面的分析带来不必要的干扰,因此需要看下有无办法去掉View结构部分。

看了下runApp的源码,发现View结构这一部分是在WidgetsBindingwrapWithDefaultView方法中被添加进来的。

再看下wrapWithDefaultView方法的注释: 由runApp用于将提供的rootWidget包装在默认View中。 View决定应用程序渲染到哪个FlutterView中。目前这是来自platformDispatcher的PlatformDispatcher.implicitView 。提供给此方法的rootWidget必须尚未包装在View中。

OK,这wrapWithDefaultView方法的注释看得一头雾水,没关系,再看下View的红框部分注释。

翻译:提供的child包装在根据给定view构造的MediaQuery中。 查看了View的部分源码发现的确如此。

这里的child指我们传入的WidgetviewFlutterView,结合之前的分析,笔者大胆猜测wrapWithDefaultView方法的用意:用View对FlutterView进行一个指定渲染的优化管理,在View中先让MediaQuery初始化,为的是让后续的子Widget能够通过MediaQuery获取相关媒体查询数据。也就是说,如果不想调用wrapWithDefaultView方法,只要保证后续子Widget没用到MediaQuery就OK。

前面所给出的MyApp代码中是完全没有用到MediaQuery的,所以这点不用担心,我们可以大大方方的不调用wrapWithDefaultView方法,改造后runApp方法如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void main() {
  _runApp(const MyApp());
}

void _runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  binding
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

但是这样还不行,因为scheduleAttachRootWidget方法是一个@protected方法,外部调用该方法的话会有警告,所以直接把方法体的代码摘抄出来就好了。

1
2
3
4
5
6
7
void _runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  Timer.run(() {
    binding.attachRootWidget(app);
  });
  binding.scheduleWarmUpFrame();
}

再次运行程序,然后通过Flutter Inspector查看Widget层次结构,发现与预期的Widget层次结构一致。

温馨提示:此处runApp方法的改动是为了在Debug调试源码过程中减少其它Widget的干扰,而在实际项目中不能这样做。

三、Root分析

OK,现在从程序入口_runApp方法开始分析。该方法接收一个Widget参数,接下来执行WidgetsFlutterBindingensureInitialized方法,返回一个WidgetsBinding实例。

2.1、WidgetsFlutterBinding.ensureInitialized()

1
2
3
4
5
6
7
8
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding._instance == null) {
      WidgetsFlutterBinding();
    }
    return WidgetsBinding.instance;
  }
}

由上面源码可知,在ensureInitialized方法中,首先会判断WidgetsBinding._instance == null,如果满足条件,则调用WidgetsFlutterBinding的构造方法进行初始化,最后返回WidgetsBinding.instance实例。

当然了,第一次调用ensureInitialized方法时WidgetsBinding._instance肯定为null,这里可以打个断点Debug看下。

此时会有个意外的发现,WidgetsFlutterBinding中根本就没有构造方法,毫无疑问会执行其父类BindingBase的构造方法。

OK,我们继续跟踪其父类BindingBase的源码。

发现父类BindingBase构造方法中有一个initInstances方法,这里打个断点Debug调试一下,进入initInstances方法。

然后进入了WidgetsBindinginitInstances方法,继续Debug下一步,来到super.initInstances()

继续Debug进入super.initInstances(),然后进入了RendererBindinginitInstances方法。

以此类推,当断点Debug调试走到BindingBaseinitInstances方法时,它的调用堆栈如下图所示。

对于出现这样栈帧顺序的方法调用栈,其实并不感到意外,回顾之前WidgetsFlutterBinding的声明处源码。

1
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding

可以发现,WidgetsFlutterBinding混入了GestureBinding、SchedulerBinding、ServicesBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding这七个Binding,并且这些Binding都是BindingBase的子类,每个Binding都重写了父类BindingBaseinitInstances方法,也在各自重写的initInstances方法体中调用了super.initInstances()以及执行了实例变量引用的赋值_instance = this

因此,initInstances方法的调用顺序为:WidgetsBinding -> RendererBinding -> SemanticsBinding -> PaintingBinding -> ServicesBinding -> SchedulerBinding -> GestureBinding,最后才调用父类BindingBase中的initInstances方法,这点从上图的方法调用栈也可以看出。

也许你会有疑问:为什么initInstances方法的调用顺序是这样的?

这其实是Dart语言中with关键字的一个语法特性了,如果一个类with混入的多个类中都有相同的方法(例如上面的initInstances方法),那么当调用该方法时,只会调用距离with关键字最远类中的方法,也就是上面WidgetsBinding中的initInstances方法。

但是由于WidgetsBindinginitInstances方法中又调用了super.initInstances(),所以才会一级一级地往父类方向调用,直到调用父类BindingBase中的initInstances方法。

因为调用的是WidgetsFlutterBinding的构造方法来进行初始化,所以ensureInitialized方法实例化的是WidgetsFlutterBinding对象了,只不过把实例引用赋值给了WidgetsBinding,如下图所示。

2.2、attachRootWidget方法

WidgetsFlutterBinding.ensureInitialized()实例化WidgetsFlutterBinding后,接下来会继续执行WidgetsBindingattachRootWidget方法,这个方法接收了一个Widget参数。

那么attachRootWidget方法做了什么事情?看下它的注释。

1
2
 /// Takes a widget and attaches it to the [rootElement], creating it if
 /// necessary.

翻译:获取一个Widget并将其附加到rootElement ,并在必要时创建它。 attachRootWidget方法的源码如下图所示。

由上面源码可知,attachRootWidget方法体中先是执行了RenderObjectToWidgetAdapter构造方法,然后再执行该实例对象的attachToRenderTree方法。

2.3、RenderObjectToWidgetAdapter构造方法

先打个断点Debug看下RenderObjectToWidgetAdapter构造方法传入的参数。

可以看到,RenderObjectToWidgetAdapter构造方法传入了3个参数,其中参数child就是我们自己的MyApp;参数container传入了renderView,它的类型为RenderView;最后一个参数debugShortDescription从名字上看是调试用的,这个不用管。

关于参数container传入了renderView,那么这个renderView是怎么来的呢?

回顾下之前所讲的那七个混入Binding之一的RendererBinding,其实renderView是在RendererBindinginitInstances方法中初始化的。

OKRenderObjectToWidgetAdapter构造方法的参数分析完毕,接下来继续看attachToRenderTree方法。

2.4、attachToRenderTree方法

先打个断点Debug看下attachToRenderTree方法传入的参数。

可以看到,attachToRenderTree方法接收2个参数,一个是BuildOwner,另一个是可选的位置参数RenderObjectToWidgetElement

关于传入的参数buildOwner,那么这个buildOwner是怎么来的呢?它是在那七个混入Binding之一的WidgetsBindinginitInstances方法中初始化的。

OK,开始分析attachToRenderTree方法源码,因为elementnull,所以重点分析下红框部分。

owner.lockState()接收一个VoidCallback回调,lockState方法体中只是进行一些断言操作,然后在try-finally中执行callback方法,lockState方法的源码如下。

lockState方法体中没做什么操作,所以lockState方法回调中先调用了createElement方法,创建Element实例后,再调用Element实例的assignOwner方法,因为assignOwner方法不是主线的重点,所以这部分不做讲解。

继续看主线的重点:createElement方法,它调用的是RenderObjectToWidgetAdapter这个WidgetcreateElement方法。

可以发现,调用了RenderObjectToWidgetElement构造方法,方法参数传入的是this,表示RenderObjectToWidgetAdapter本身。

RenderObjectToWidgetElement构造方法参数执行super.widget,就会把widget一级一级往上传递,先是执行了 RenderObjectElement的构造方法,最后执行了Element的构造方法。并且在Element的构造方法中,把一路传递过来的this,也就是RenderObjectToWidgetAdapter本身赋值给了Element的成员变量_widget,那么Element就持有了这个Widget

OKowner.lockState()已经分析完成,继续看owner.buildScope()buildScope方法体的实现与前面的lockState方法较为相似,也只是进行一些断言判断,最后在try-finally中执行callback方法。

buildScope方法体中没做什么操作,所以在buildScope方法回调中就会执行elementmount方法,也就是RenderObjectToWidgetElementmount方法。

分析下RenderObjectToWidgetElementmount方法,这里分两步走,一步是执行super.mount,也就是执行父类的mount方法;另一步是执行_rebuild方法,这个后面会讲。

看下第一步,也就是执行父类的mount方法,它会一级一级往上执行,先是执行RenderObjectElementmount方法,最后执行Elementmount方法。

可以看到,在Elementmount方法中主要是对一些属性进行赋值操作,如_parent等。当Elementmount方法执行完成后就会出栈,然后回来执行RenderObjectElementmount方法。

RenderObjectElementmount方法中会执行widgetcreateRenderObject方法,想必该Widget是谁应该知道吧,它就是之前在构造方法中将参数一级一级往上传递的this,也就是RenderObjectToWidgetAdapter,它是RootWidget,如果不确定,可以Debug断点调试看下。

继续执行RenderObjectToWidgetAdaptercreateRenderObject方法,会返回一个container,也就是之前讲的RenderView

然后将createRenderObject方法返回值赋值给RenderObjectElement的成员变量_renderObject,于是得出一个结论:RenderObjectElement会持有一个RenderObject实例。

接着,继续执行RenderObjectElement中的attachRenderObject方法。

RenderObjectElement中的attachRenderObject方法中,它一共做了三件事情。

1、执行_findAncestorRenderObjectElement方法,沿着父类往上查找最远处RenderObjectElement类型的Element,并且赋值给 _ancestorRenderObjectElement;其中_parent是在Elementmount方法中赋值,一开始_parentnull,所以_findAncestorRenderObjectElement方法返回为null,相当于该方法没执行。

2、执行上一步返回的_ancestorRenderObjectElementinsertRenderObjectChild方法将renderObject插入渲染树中; 但是由于_ancestorRenderObjectElementnull,所以insertRenderObjectChild方法并没有触发。

3、执行_findAncestorParentDataElement方法,沿着父类往上查找最近处ParentDataElement类型的Element,但是由于_parentnull,所以该方法执行了个寂寞。

因此,整体上RenderObjectElement中的attachRenderObject方法相当于没执行到。

OKRenderObjectElementmount方法分析完了,此时会继续出栈,然后继续执行RenderObjectToWidgetElementmount方法中super.mount()之后的逻辑。

也就是执行RenderObjectToWidgetElement_rebuild方法,在 _rebuild方法中,执行了updateChild方法,参数一_childnull,参数二就是我们自己的MyApp,参数三为Object

继续执行ElementupdateChild方法,因为此时childnull,所以会执行红框部分,也就是执行inflateWidget方法。

继续执行ElementinflateWidget方法,之前已经知道传入的newWidgetMyApp,看下它的源码。

Element中的inflateWidget方法中,它一共做了3件事情。

1、校验key是否为GlobalKey,如果是就会做相应逻辑,因为不是此次主线重点,所以不做讲解。

2、执行newWidgetcreateElement方法,也就是执行MyAppcreateElement方法,返回一个Element实例。

3、执行上一步返回Element实例的mount方法。

看到这里,相信你会觉得createElementmount方法似曾相识,因为之前分析过了。

总结下Root分析的执行流程,如下图所示。

四、StatelessWidget分析

接着Root分析,继续执行MyAppcreateElement方法,因为MyApp的类型是StatelessWidget,所以执行了StatelessWidgetcreateElement方法。

然后创建了StatelessElement实例,并将this传入StatelessElement的构造方法中,this也就是MyApp本身,而StatelessElement构造方法的参数执行了super.widget,会一级一级地向父类传递this,先是执行了ComponentElement的构造方法,最后再执行Element的构造方法,将传递的this赋值给Element的成员变量_widget

这种子类Element向父类一级一级传递this的行为我们之前讲过的,也是似曾相识了。

接着Root分析,继续执行Elementmount方法。因为StatelessElement没有mount方法,所以执行的是ComponentElementmount方法。

可以看到,先是执行了super.mount,也就是执行Elementmount方法,这个之前看过,只是一些成员变量的赋值操作。

然后执行了_firstBuild方法,在该方法中又执行了Elementrebuild方法。

Elementrebuild方法中,try-finally处执行了performRebuild方法,然后又回到了ComponentElementperformRebuild方法。

ComponentElementperformRebuild方法中,做了两件事情。

1、执行build方法。这里执行了StatelessElementbuild方法。

这里的widget就是我们自己的MyApp,所以执行了MyAppbuild方法,创建了MyPage实例。

2、执行了ElementupdateChild方法,看到该方法是否又似曾相识,OK,继续分析。

因为此时childnull,所以会执行红框部分,也就是执行inflateWidget方法。

继续执行ElementinflateWidget方法,之前已经知道传入的newWidgetMyPage,看下它的源码。

Element中的inflateWidget方法中,它一共做了三件事情,这个前面讲过。

1、校验key是否为GlobalKey,如果是就会做相应逻辑,因为不是此次主线重点,所以不做讲解。

2、执行newWidgetcreateElement方法,也就是执行MyPagecreateElement方法,返回一个Element实例。

3、执行上一步返回Element实例的mount方法。

看到这里,相信你会觉得再熟悉不过,因为之前多次分析过createElementmount方法了。

五、StatefulWidget分析

接着StatelessWidget分析,继续执行MyPagecreateElement方法,因为MyPage的类型是StatefulWidget,所以执行了StatefulWidgetcreateElement方法。

然后创建了StatefulElement实例,并将this传入StatefulElement的构造方法中,this也就是MyPage本身。

StatefulElement的构造方法中,执行了widget.createState(),widget就是我们的MyPage,也就是执行了MyPagecreateState方法。

将创建好的_MyPageState实例赋值给StatefulElement的成员变量_state,也就是说StatefulElement持有了State实例。

与此同时,StatefulElement构造方法中还执行了super(widget),会一级一级地向父类传递this,先是执行了ComponentElement的构造方法,最后再执行Element的构造方法,将传递的this赋值给Element的成员变量_widget

这种子类Element向父类一级一级传递this的行为我们之前讲过的,也是老相识了。

接着StatelessWidget分析,继续执行Elementmount方法。因为StatefulElement没有mount方法,所以执行的是ComponentElementmount方法。

可以看到,先是执行了super.mount,也就是执行Elementmount方法,这个之前看过,只是一些成员变量的赋值操作。

然后执行了_firstBuild方法,在该方法中又执行了Elementrebuild方法。

Elementrebuild方法中,try-finally处执行了performRebuild方法,然后又回到了ComponentElementperformRebuild方法。

ComponentElementperformRebuild方法中,做了两件事情。

1、执行build方法。这里执行了StatefulElementbuild方法。

这里执行了state.build(),也就是执行了_MyPageStatebuild方法,创建了ColoredBox实例。

2、执行了ElementupdateChild方法,该方法是老朋友了。

再后面就是ColoredBox的分析,因为ColoredBox最终父类的类型与根布局RenderObjectToWidgetAdapter的类型都是RenderObjectWidget,因此分析都是差不多的,后面就留给你们发挥了。

当然了,runApp方法的分析肯定不止上面这些内容,后续有机会再补上。