2.学习Flutter -- 启动过程做了什么

通过一系列文章记录一下学习 Flutter 的过程,总结一下相关知识点。

  1. 学习Flutter -- 框架总览
  2. 学习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 在初始化阶段做的事情,总结强调下:

  1. 把 window 提供的 api 分别封装到不同的 Binding 中;
  2. 初始化了两个重要的单例对象 PipelineOwner 和 BuildOwner,在渲染中起着重要的作用。
  3. 初始化了 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 进而显示到屏幕上。

总结

  1. Window 是 Flutter framework 与 Flutter engine 之间的桥梁,确保了Flutter 与宿主工程之间的通信;
  2. 启动流程 Flutter framework 各功能的初始化主要通过多个 Binding 实现;
  3. 启动流程初始化了两个重要的 Owner:PipelineOwner 和 BuildOwner;
  4. 启动流程初始化了三棵树的根节点:对应的类型分别是RenderObjectToWidgetAdapter、RenderObjectToWidgetElement、RenderView;

本篇主要从源码的角度,学习了一下 Flutter 启动过程中调用到的方法流程,以及所做的事情,最后总结如图:

相关推荐
君蓦2 小时前
Flutter 本地存储与数据库的使用和优化
flutter
problc11 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
lqj_本人19 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人1 天前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔1 天前
Flutter启动流程(2)
flutter
hello world smile1 天前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人1 天前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai1 天前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人1 天前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos