Flutter入口中的runApp方法解析

1,224 阅读3分钟

前言

开发中,如果在runApp方法执行之前设置Android沉浸式样式报错,需要先设置WidgetsFlutterBinding.ensureInitialized();这一行代码才行,为什么,接下来看下这一行代码具体做了啥。

点进去发现这个方法在runApp中进行了实现,并且还调用了WidgetsFlutterBinding的另两个方法,
方法体:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

接下来我们对这三个方法进行一个一个进行分析。

1、WidgetsFlutterBinding.ensureInitialized()

代码:


/// widgets框架的具体绑定,将框架绑定到Flutter引擎的中间层。
/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
class WidgetsFlutterBinding extends BindingBase with GestureBinding,
SchedulerBinding, ServicesBinding, PaintingBinding,
SemanticsBinding, RendererBinding, WidgetsBinding {

/// 只有需要绑定时,才需要调用这个方法,在runApp之前调用。
/// You only need to call this method if you need the binding to be
/// initialized before calling [runApp].

static WidgetsBinding ensureInitialized() {
  if (WidgetsBinding.instance == null)
    WidgetsFlutterBinding();
  return WidgetsBinding.instance!;
}
}

可以看到,WidgetsFlutterBinding 继承了 BindingBase,并混入了一些其他Binding

先看下BindingBase类,

/// 初始化 获取唯一window
ui.SingletonFlutterWindow get window => ui.window;
/// 初始化PlatformDispatcher实例,平台消息和配置的中心入口点,负责分发事件给Flutter引擎。
ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;

主要是获取了window实例和PlatformDispatcher实例。

再看下其他Binding解释:

  • GestureBinding:处理手势相关。

  • SchedulerBinding: 处理系统调度。

  • ServicesBinding:处理与原生的交互。

  • PaintingBinding:处理绘制相关。

  • SemanticsBinding:处理语义化。

  • RendererBinding:处理渲染相关。

  • WidgetsBindingWidgets相关。

Flutter框架层的相关基础绑定。

接着我们看下改变状态栏的代码。

改变样式核心代码:

if (_pendingStyle != _latestStyle) {
// 通过和原生平台进行通信 来改变具体平台状态样式
  SystemChannels.platform.invokeMethod<void>(
    'SystemChrome.setSystemUIOverlayStyle',
    _pendingStyle!._toMap(),
  );
  _latestStyle = _pendingStyle;
}

通过 ensureInitialized的注释和修改样式的代码即可解决我们开头的疑问,因为设置状态栏样式是通过原生window窗口进行修改的,所以这里如果需要修改状态栏,就需要进行和原生绑定才能拿到原生的window窗口来进行修改。

从注释来看:WidgetsFlutterBinding是widgets框架的具体绑定,将框架绑定到Flutter引擎的中间层。

通过 ensureInitialized方法返回 一个WidgetsBinding单例类。

至此,WidgetsFlutterBinding.ensureInitialized();的工作已经结束。
就是做了初始化引擎绑定,返回WidgetsBinding

..scheduleAttachRootWidget(app)

上一个方法我们知道返回了WidgetsBinding类,那这个方法就是在WidgetsBinding这个类里,接下来先看下这个类。

/// widgets和Flutter引擎之间的粘合剂,中间层
/// 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;
    // Initialization of [_buildOwner] has to be done after
    // [super.initInstances] is called, as it requires [ServicesBinding] to
    // properly setup the [defaultBinaryMessenger] instance.
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    
/// 

@protected
void scheduleAttachRootWidget(Widget rootWidget) {
  Timer.run(() {
    attachRootWidget(rootWidget);
  });
}

void attachRootWidget(Widget rootWidget) {
  final bool isBootstrapFrame = renderViewElement == null;
  _readyToProduceFrames = true;
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
  if (isBootstrapFrame) {
    SchedulerBinding.instance!.ensureVisualUpdate();
  }
}

可以看到这是一个mixin类,创建了BuildOwner的实例,这个类是用来管理Element的,在attachRootWidget方法中创建了RenderObjectToWidgetAdapter实例,并设置了我们的runApp中的参数根节点rootWigdt

/// 桥接 RenderObject 到 Element 
/// A bridge from a [RenderObject] to an [Element] tree.
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
  }) : super(key: GlobalObjectKey(container));

RenderObjectToWidgetAdapter 是桥接RenderObjectElement的,Element是持有Widget的具体实现,通过RenderObject进行渲染,也就是通过这个方法实现了 Widget、Element、RenderObject的初始及绑定关系。

..scheduleWarmUpFrame();

绑定之后,接下来就是将内容显示在屏幕上,从以下代码分别调用了handleBeginFramehandleDrawFrame方法,通过hadScheduledFrame判断是否调用handleBeginFrame触发scheduleFrame方法,调用 window.scheduleFrame(); 最终调用 platformDispatcher.scheduleFrame();通知引擎在合适的时机进行帧绘制。

void scheduleWarmUpFrame() {
  if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
    return;
  _warmUpFrame = true;
  final TimelineTask timelineTask = TimelineTask()..start('Warm-up frame');
  final bool hadScheduledFrame = _hasScheduledFrame;
  // We use timers here to ensure that microtasks flush in between.
  Timer.run(() {
    assert(_warmUpFrame);
    handleBeginFrame(null);
  });
  Timer.run(() {
    assert(_warmUpFrame);
    handleDrawFrame();
 
    resetEpoch();
    _warmUpFrame = false;
    if (hadScheduledFrame)
      scheduleFrame();
  });
  
  

void scheduleFrame() {
  window.scheduleFrame();
}

/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
void scheduleFrame() => platformDispatcher.scheduleFrame();

小结

runApp中的三个方法执行的三步分别是:
1、初始化WidgetsFlutterBinding返回WidgetBinding实例。
2、初始化Widget、Elment、RenderObject三棵树并确定绑定关系。
3、通知引擎合适时机进行帧绘制。更快的将内容显示到屏幕中。