Flutter-runApp()方法

Jason的Home發表於2021-07-26

Flutter程式的入口main()方法會呼叫runApp()方法,我們本篇探索runApp都做了啥。

概覽

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

這個方法一看很簡潔,一共呼叫了WidgetsFlutterBinding的三個方法,在看三個方法的實現之前,我們看一下WidgetsFlutterBinding是什麼。

WidgetsFlutterBinding

我們看一下官方的解釋:

A concrete binding for applications based on the Widgets framework.This is the glue that binds the framework to the Flutter engine.

翻譯過來就是,一個基於Widgets framework的應用程式的具體繫結,它是繫結frameworkFlutter engine的膠水層。

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}
複製程式碼

它的父類BindingBase是一個抽象類,with實現了很多mixin,這些mixin只能用於繼承自BindingBase的類。mixin的作用是擴充套件功能,mixin可以類比於iOSprotocol(個人見解,如果不對歡迎指正)。

BindingBase

abstract class BindingBase {
  /// 省略程式碼
  /// However, multiple window support is not yet implemented, so currently this
  /// provides access to the one and only window.
  // TODO(gspencergoog): remove the preceding note once multi-window support is
  // active.
  // 唯一的window
  ui.SingletonFlutterWindow get window => ui.window;
  /// 每一個BindingBase類定義行為 都有一個 platformDispatcher 作為回撥(handlers)
  ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
  /// 初始化例項
  void initInstances() {
    assert(!_debugInitialized);
    assert(() {
      _debugInitialized = true;
      return true;
    }());
  }
  /// The current [WidgetsBinding], if one has been created.
  /// ensureInitialized方法返回的例項
  /// If you need the binding to be constructed before calling [runApp],
  /// you can ensure a Widget binding has been constructed by calling the
  /// `WidgetsFlutterBinding.ensureInitialized()` function.
  static WidgetsBinding? get instance => _instance;
  static WidgetsBinding? _instance;
  /// 註冊 service extensions 初始化之後呼叫
  void initServiceExtensions() {
    ///省略程式碼
  }
}
複製程式碼
  • ui.window:是Flutter App顯示的視窗,它繼承自FlutterView,位於Flutter engine層。
  • ui.PlatformDispatcher.instance:platformDispatcher是Flutter 的一個事件分發器,負責Flutter分發engine的事件,和傳遞事件給engine層。
  • initInstances:初始化例項的方法。
  • initServiceExtensions():註冊 service extensions,比如platformOverrideactiveDevToolsServerAddress等。

ensureInitialized()方法

該方法的作用是返回一個WidgetsBinding型別例項,如果未建立就新建立一個。

static WidgetsBinding ensureInitialized() {
  if (WidgetsBinding.instance == null)
    WidgetsFlutterBinding();
  return WidgetsBinding.instance!;
}
複製程式碼

就是返回一個WidgetsBinding.instance例項,因為WidgetsFlutterBinding實現了GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBindingmixin,mixin的initInstancesinitServiceExtensions方法也會呼叫,每個mixin的功能:

  • GestureBinding:處理手勢。
  • SchedulerBinding: 處理系統排程。
  • ServicesBinding:處理與原生的互動。
  • PaintingBinding:處理繪製。
  • SemanticsBinding:處理語義化。
  • RendererBinding:處理渲染。
  • WidgetsBindingWidgets相關。

我們下面主要看WidgetsBindingRendererBinding

WidgetsBinding

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    assert(() {
      _debugAddStackFilters();
      return true;
    }());
    // 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;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
 }
  ///省略程式碼
}
複製程式碼

WidgetsBinding初始化會建立一個BuildOwner物件,它的作用是管理Widget樹和Element樹。

RendererBinding

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }
​
  /// The current [RendererBinding], if one has been created.
  static RendererBinding? get instance => _instance;
  static RendererBinding? _instance;
  ///省略程式碼
}
複製程式碼

RendererBinding初始化會建立一個PipelineOwner物件,用於管理RenderObject樹。PipelineOwnerBuildOwner都位於framework層,它們通過Bingding(膠水層)與engine互動。

  • 初始化了一個PipelineOwner用於管理RenderObject.
  • _handlePersistentFrameCallback這個callback傳入SchedulerBinding中的_postFrameCallbacks中,這樣在硬體每次發出VSync訊號的時候都會呼叫RenderBinding中的_handlePersistentFrameCallback方法._handlePersistentFrameCallback方法中直接呼叫了drawFrame方法。

scheduleAttachRootWidget

例項化之後會呼叫scheduleAttachRootWidget方法。

  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }
複製程式碼

呼叫了attachRootWidget方法:

  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();
    }
  }
複製程式碼

attachRootWidget方法用於是為根Widget生成一個根Element。生成Element呼叫了attachToRenderTree方法並傳入了BuildOwner和Element。

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      ///新建立一個 element
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element!.assignOwner(owner);
      });
      // 建立能更新widget 樹的能力,可以回撥 callback,構建所有標記為dirty的elment
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
 }
複製程式碼

這個方法主要是element為空的時候新建一個element,新建後會呼叫BuildOwnerbuildScope主要作用是建立能更新widget 樹的能力,可以回撥 callback,構建所有標記為dirtyelement

attachRootWidget方法,最後會執行SchedulerBinding.instance!.ensureVisualUpdate()

  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }
複製程式碼

它主要是呼叫新的幀的排程管理。它會呼叫scheduleFrame方法

  void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      return true;
    }());
    ///給window設定回撥
    ensureFrameCallbacksRegistered();
    ///排程更新
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
複製程式碼

這裡會給window設定onBeginFrameonDrawFrame的回撥,window會把回撥傳給platformDispatcher

  @override
  set onBeginFrame(ui.FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
  @override
  ui.VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
  @override
  set onDrawFrame(ui.VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
  }
複製程式碼

也就是說scheduleAttachRootWidget經過一系列呼叫之後,會把SchedulerBinding_handleBeginFrame_handleDrawFrame傳給platformDispatcherplatformDispatcher分發來自enginee的事件。而在這裡SingletonFlutterWindowplatformDispatcheronBeginFrameonDrawFrame這兩個事件交給SchedulerBinding處理。

當硬體發出VSync訊號時,會呼叫platformDispatcher的onDrawFrame。實際上會呼叫SchedulerBinding中的_handleDrawFrame方法。_handleDrawFrame會呼叫handleDrawFrame方法:

  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    } finally {
      ///省略程式碼
    }
  }
複製程式碼

_postFrameCallbacks裡面儲存的是callback,作用是硬體每次發出VSync訊號的時候都會呼叫。這裡的_postFrameCallbacks是在RenderBinding這個mixininitInstances方法中傳入的

addPersistentFrameCallback(_handlePersistentFrameCallback);
複製程式碼

scheduleWarmUpFrame

void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;
    _warmUpFrame = true;
    Timeline.startSync('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();
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });
    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }
複製程式碼

這個方法主要呼叫是scheduleFrame,跟進程式碼實際是呼叫的window.scheduleFrame(),

  @override
  void scheduleFrame() {
    platformDispatcher.scheduleFrame();
  }
複製程式碼

window.scheduleFrame()呼叫了platformDispatcher.scheduleFrame(),通知engine層需要繪製。engine會根據情況儘快地呼叫platformDispatcher的onDrawFrame方法。

總結

runApp方法主要做了以下事情:

  • 建立WidgetsFlutterBinding它是連線frameworkengine的膠水層。註冊Vsync回撥,後面每一幀的呼叫會出發WidgetsFlutterBinding的回撥,最後傳遞到framework層處理邏輯。
  • attachRootWidget:遍歷掛載整個檢視樹,建立widgetelementrenderObjcect的連線關係。
  • scheduleWarmUpFrame:排程幀預熱(warmUp)。執行幀繪製handleBeginFramehandleDrawFrame方法。

相關文章