通过一系列文章记录一下学习 Flutter 的过程,总结一下相关知识点。
- 学习Flutter -- 框架总览
- 学习Flutter -- 启动过程做了什么
本篇主要从源码的角度,学习了一下 Flutter 启动过程中调用到的方法,以及各个阶段所做的事情。
在前端开发中,我们对窗口(Window)的概念并不陌生, 我们写的 UI 程序都会显示在窗口中,窗口是框架运行的基础,用户界面的绘制、手势事件的处理等都需要通过窗口来管理,当然Flutter 也不例外。
Window
在 Flutter 中,Window 类是 Flutter Framework 与宿主操作系统之间的接口。宿主操作系统通过 Window 类开发了各种回调方法,以及 Flutter Framework 也是通过 Window 调用到 Native 侧的接口的。
源码如下:
dart
(位置:dart:ui库中的 window.dart):
//全局单例
final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);
class SingletonFlutterWindow extends FlutterWindow {
// 当前系统默认的语言Locale
Locale get locale => platformDispatcher.locale;
// 当前系统字体缩放比例。
double get textScaleFactor => _textScaleFactor;
// Locale发生变化回调
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系统字体缩放变化回调
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
FrameCallback get onBeginFrame => _onBeginFrame;
// 绘制回调
VoidCallback get onDrawFrame => _onDrawFrame;
// 点击或指针事件回调
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 请求engine调度一帧,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
// 此方法会直接调用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 绘制完成后将场景送入engine显示
// 此方法会直接调用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 发送平台消息, 这个是Platform channels机制的一部分
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 收到平台消息的回调
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它属性及回调
}
从源码中发现,Flutter 中操作的 window 对象是一个全局的单例对象,它对上层提供了屏幕尺寸等系统相关的参数、输入事件的回调、图形渲染接口等一些核心服务,以及向平台发送消息等功能。
在 Flutter 中,我们需要响应各种手势事件实现不同的功能,那么手势事件是如何产生的呢? 从上面 Window 的源码其实已经找到了答案,下面通过一个具体的例子看一下上面 window 中的回调是如何使用的。
测试案例
可通过 window 中的 onPointerDataPacket 这个回调函数,则在 Flutter 侧可以接收到系统底层的传递过来的事件。
dart
void main() {
window.onPointerDataPacket = (PointerDataPacket packet) {
print(packet.data);
};
}
运行后,当屏幕有手势事件时,则会触发该回调,控制台打印的原始数据如下:
这个简单的测试例子只是说明了 window 中的回调是如何被使用的,当然在 Flutter Framework 中会通过更复杂的封装和分发,对引擎传递过来的原始数据进行充分利用,从而满足上层的组件开发使用。
系统案例
在 Flutter Framework 的 GestureBinding 中的 initInstance 方法中设置了手势事件的回调,具体的实现细节可查看GestureBinding 的内部源码。
dart
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
void initInstances() {
//..省略
window.onPointerDataPacket = _handlePointerDataPacket;
}
//回调事件
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
// We convert pointer data to logical pixels so that e.g. the touch slop can be
// defined in a device-independent manner.
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked) {
_flushPointerEventQueue();
}
}
...//
}
从Window 源码中我们知道不只有手势事件的回调,还提供了屏幕尺寸、图形绘制接口和一些其他的核心服务。
小结
-
Flutter Framework 的运行是以 Window 为根基的,Window 是 Engine 与 Framework 之间的桥梁,确保了Engine 与 Framework 之间的通信;
-
Window 是对 Flutter Engine 中的图形界面相关的接口的封装(当然基于 window 也可以搭建属于自己的一套框架取代 Flutter Framework)
-
最后用一张图来总结一下 Window 的作用:
至此,简要介绍来了一下 Window 在 Flutter 开发中的重要作用。
一个 Flutter 应用是怎么跑起来的呢? Flutter 页面是如何显示到屏幕上的呢? 接下来将从源码的角度去分析一下它是怎么运行的。
启动流程
main & runApp
Flutter 应用启动的入口在 main.dart 的 main() 函数中,这里是应用程序的起点,main 函数的实现如下:
dart
void main() => runApp(MyApp());
可以看出 main 函数只是调用了 runApp 方法,继续看看 runApp 方法的具体实现:
dart
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()//1.
..scheduleAttachRootWidget(app)//2.
..scheduleWarmUpFrame();//3.
}
其中,参数 Widget app 是应用启动后要展示的第一个组件( 也就是MyApp),方法内部共有三个方法调用,接下来按标号顺序具体展开介绍下。
WidgetsFlutterBinding
WidgetsFlutterBinding 是绑定 Widgets Framework 和 Flutter Engine 之间的"胶水",通过混入多个 Binding,提供了 Window 对象内部的各种回调的处理。
其内部只有一个 ensureInitialized 方法用于获取 WidgetsBinding 类型的单例,源码定义如下:
dart
//WidgetsFlutterBinding
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null) {
WidgetsFlutterBinding();
}
return WidgetsBinding.instance;
}
}
该类继承自 BindingBase 并且混入了多个Binding。
第 6 行 WidgetsFlutterBinding 的初始化会调用父类 BindingBase 的构造函数, 构造函数内又分别调用了 initInstances 和 initServiceExtensions 方法。
dart
//基类 BindingBase
abstract class BindingBase {
BindingBase() {
initInstances();
initServiceExtensions();
}
}
//上述提到的 window
ui.SingletonFlutterWindow get window => ui.window;
WidgetsFlutterBinding 类混入的多个Binding,经查看源码,各个Binding 又分别重写了 initInstances 和 initServiceExtensions 方法。
故,在 WidgetsFlutterBinding 的构造过程中,又分别执行了各个 Binding 的 initInstances 和 initServiceExtensions 方法(如果有的话),最后执行顺序是这样的:
BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBinding -> WidgetsBinding
知识点: 由于方法内部分别调用了 super.initXX 方法,所以会按照 mixin 的顺序依次调用各个 Binding 的 initXX 方法。
Binding
上述提到的各个 Binding 分别有什么作用呢?查看这些 Binding 源码发现,这些 Binding 基本都是用于监听和处理 Window 对象的一些事件,然后再将这些事件进行包装、抽象、分发处理,以及一些其他初始化。
GestureBinding
手势绑定
作用:主要用于管理手势事件,实现 Flutter Framework 事件模型与系统底层事件的绑定,主要设置了 window.onPointerDataPacket 的回调函数。
dart
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
SchedulerBinding
调度绑定
作用:用于监听绘制刷新事件。
dart
mixin SchedulerBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
}
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
ServicesBinding
服务绑定
作用:用于处理 Na 与 Flutter 通信。
dart
mixin ServicesBinding on BindingBase, SchedulerBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
// window 回调
window.onKeyData = _keyEventManager.handleKeyData;
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
}
PaintingBinding
绘制绑定
作用:主要用于处理图片缓存
dart
mixin PaintingBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
_imageCache = createImageCache();
shaderWarmUp?.execute();
}
SemanticsBinding
辅助功能绑定
作用:是语义化层与Flutter engine 之间的桥梁。
dart
/// The glue between the semantics layer and the Flutter engine.
mixin SemanticsBinding on BindingBase {
@override
void initInstances() {
super.initInstances();
_instance = this;
//
_accessibilityFeatures = window.accessibilityFeatures;
}
RendererBinding
渲染绑定(重要)
作用:是渲染树与Flutter engine 之间的桥梁,管理渲染流程,初始化的时候做的事情比较多:
- 初始化了 PipelineOwner 实例,该类用于管理驱动渲染流水线
- 设置了 window 渲染相关的一系列回调函数(屏幕尺寸、亮度等)
- 初始化 RenderView 实例, 该实例就是 Render Tree 的根节点
- 通过 addPersistentFrameCallback 添加了一个帧回调函数,重点:渲染流水线的启动阶段都在这个函数中启动;
dart
/// The glue between the render tree and the Flutter engine.
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
//
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsUpdate: _handleSemanticsUpdate,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
//window 回调
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
//初始化 renderView,根 RenderObject
initRenderView();
//帧回调函数
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
}
//
void initRenderView() {
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
WidgetsBinding
组件绑定(重要)
作用:是 Widget 层与Flutter engine 之间的桥梁,初始化阶段主要做了:
- 初始化 BuildOwner 实例,同时设置了 onBuildScheduled 回调,该实例主要负责管理 Widget 的重建,跟踪哪些 widget 需要重新构建。
- 设置了 window 相关回调
- 设置处理路由的回调;
ini
/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
//window 回调
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
//
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
}
小结
以上通过源码的解读,了解了下各个 Binding 在初始化阶段做的事情,总结强调下:
- 把 window 提供的 api 分别封装到不同的 Binding 中;
- 初始化了两个重要的单例对象 PipelineOwner 和 BuildOwner,在渲染中起着重要的作用。
- 初始化了 Render Tree 的根节点 RenderView 对象;
attachRootWidget
上述的 WidgetsFlutterBinding.ensureInitialized() 负责初始化了 WidgetBinding 的全局单例。紧接着调用 WidgetBinding 的 attachRootWidget 方法。
源码:
scala
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
//RenderObjectWidget 的子类
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget 「
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
@override
RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
...
}
attachRootWidget 方法的主要作用:
- 初始化根 Widget,类型是 RenderObjectToWidgetAdapter,继承自RenderObjectWidget,而我们传入的 MyApp 作为子 widget。
- 根 Widget与 RenderView进行关联 (RenderView 继承自 RenderObject, 就是Render tree的根,即根 Widget 与根 RenderObject 关联)
- 随之调用 attachToRenderTree 方法,源码如下
attachToRenderTree
源码:
ini
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
attachToRenderTree 方法的作用:
- 该方法做的事情的就是渲染流水线的构建(Build)阶段,根据 widget 生成对应的 element tree 和 render tree(Element类型是 RenderObjectToWidgetElement)
- 此方法也可看出 Element 只会创建一次,后面会复用。
小结
至此,三棵树的根节点都已初始化并完成关联,三棵树根节点对应的类型分别是RenderObjectToWidgetAdapter、RenderObjectToWidgetElement、RenderView。
scheduleWarmUpFrame
以上是完成了构建(Build)阶段,接下来就是要进入布局(Layout)和渲染(Paint)阶段了。即调用了scheduleWarmUpFrame 方法(该方法实现在 SchedulerBinding 中)
源码:
scss
void scheduleWarmUpFrame() {
...
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
});
...
}
scheduleWarmUpFrame 方法内只调用两个方法 handleBeginFrame 和 handleDrawFrame(注: 这两个方法其实就是 window 的两个回调 onBeginFrame 和 onDrawFrame 的实现), 该方法被调用后,会立即进行一次绘制,最终将渲染出来的首帧场景传给 engine 进而显示到屏幕上。
总结
- Window 是 Flutter framework 与 Flutter engine 之间的桥梁,确保了Flutter 与宿主工程之间的通信;
- 启动流程 Flutter framework 各功能的初始化主要通过多个 Binding 实现;
- 启动流程初始化了两个重要的 Owner:PipelineOwner 和 BuildOwner;
- 启动流程初始化了三棵树的根节点:对应的类型分别是RenderObjectToWidgetAdapter、RenderObjectToWidgetElement、RenderView;
本篇主要从源码的角度,学习了一下 Flutter 启动过程中调用到的方法流程,以及所做的事情,最后总结如图: