【Flutter】runApp 流程
不管是使用 Flutter 开发完整应用还是作为区块嵌入某个页面,都需要调用 runApp
,而我们需要做的只是传入一个 Widget 实例,那么 runApp
背后所做的工作就很有趣了,比如触发视图的构建、更新以及绘制等,下面我们就粗略地看一下这个函数到底做了啥。
🧐 runApp 声明
先看一下 runApp
的函数声明:
1 | void runApp(Widget app) { |
WidgetsFlutterBinding
使用了 mixin,将负责点击事件、绘制的 XXXBinding 组合起来,这个类也使用了单例模式,即 ensureInitialized
。
调用 attachRootWidget
将会触发 Widget、Element、RenderObject 的构建
调用 scheduleWarmUpFrame
会触发视图的第一帧上屏,同时因为是第一帧,也会伴随着一些初始化操作,所以这个函数相对一般的绘制耗时较长
🤓 attachRootWidget
从 runApp
声明了解到 WidgetFlutterBinding
真正启动 Flutter 应用的入口,接着看 attachRootWidget
函数:
1 | void attachRootWidget(Widget rootWidget) { |
RenderObjectToWidgetAdapter
继承于 RenderObjectWidget
,只是起到一个 桥接 的作用,正如它的类名一样,而起到桥接作用的正是 attachToRenderTree
函数:
1 | RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) { |
应用第一次启动时,传入的 element
为 null
,所以这里分析的是第一个分支。首先会创建 RenderObjectToWidgetElement
实例,然后调用 assignOwner
进行赋值(这个 buildOwner 非常重要,整个 Widget 树中的节点都会持有同一个实例),最后会调用 mount
挂载成为 ElementTree 的根结点。
在看 RenderObjectToWidgetElement#mount
之前,先看下它的继承链:
1 | Element |
先看 RenderObjectToWidgetElement.mount
:
1 | void mount(Element parent, dynamic newSlot) { |
这里通过updateChild
方法创建 child,这里会进行一个 递归调用 完成整个树构建/更新。
然后是 RootRenderObjectElement.mount
方法:
1 | void mount(Element parent, dynamic newSlot) { |
这里只是做了简单的参数检查。
有些文章把
assert
语句去掉了,其实有一些是不能省略的,使用assert
做一些工作是为了提升性能,因为在开发阶段完成检查后,生产环境就可以避免这些操作而提高性能了
接着看 RenderObjectElement.mount
:
1 | void mount(Element parent, dynamic newSlot) { |
这个类类名有 RenderObject 字眼,恰好 mount
方法里创建了 RenderObject
实例。
最后就是 Element
基类了,这个类的 mount
方法很简单,就是针对树这种数据结构完成子节点的挂载。
所以,attachRootWidget
会完成 Widget、Element、RenderObject 树🌲的创建,这里要注意的是只是完成的和创建,并没有的进行测量、布局等,这些都是在下一个函数调用中进行的。
😎 scheduleWarmUpFrame
完成了三颗🌲的构建,接下里的就是的完成 Flutter 的上屏,即绘制、渲染。
直接看 scheduleWarmFrame
的函数定义:
1 | void scheduleWarmUpFrame() { |
上面代码做了一定程度精简。
在开始分析这个函数之前,先了解下一些 Callbacks ,上面函数声明在 SchedulerBinding
里,这个 mixin 主要负责一些任务调度,这些任务都以 Callback 的形式存在,有四种任务类型:
- Transient callbacks: 由
Window.onBeginFrame
触发,目的是同步应用状态和显示,比如动画 - Persistent callbacks: 由
Window.onDrawFrame
触发,更新显示,渲染任务 - Post-frame callbacks: 在 Persistent callbacks 执行完后执行,有且只执行一次
- Non-rendering tasks: 普通任务,它们会在两帧之间按照优先级顺序被执行
那么 Window.onXXXFrame
又是什么?
1 | mixin SchedulerBinding{ |
其实就是 ScheduleBinding
的方法,知道这些了我们接着前面继续分析:
1 | void handleBeginFrame(Duration rawTimeStamp) { |
所以 scheduleWramUpFrame
最终会执行这些回调,渲染、绘制将由这些回调/任务来完成,那么这些回调又是在哪里注册的呢?
因为负责渲染的回调/任务属于 persistent callback ,所以可以到 RendererBinding
中找下 addPersistentFrameCallback
的调用,果然在初始化的时候进行了注册:
1 | mixin RendererBinding on BindingBase, SchedulerBinding,... { |
Recap
到这里,从启动到第一帧上屏就算是完了,整个流程非常清晰。在分析的过程中,我们忽略了 BuildOwner
和 PipelineOwner
,这两个类贯穿树的构建和绘制,不过这篇文章对它们进行深入分析并不合适,之后会通过分析界面更新来分析它们的作用。
还有一点值得提起的是,除了 Widget、Element、RenderObject 树🌲之后,还有一颗 Layer 🌲。Flutter 会根据这颗树构建一个 Scene
,最后渲染并上屏。