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 启动过程中调用到的方法流程,以及所做的事情,最后总结如图:

相关推荐
我要最优解41 分钟前
关于在mac中配置Java系统环境变量
java·flutter·macos
江上清风山间明月1 天前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能2 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人2 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen2 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang2 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang2 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1232 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-2 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11193 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins