The main() method of the Flutter program calls the runApp() method, and we’ll explore what runApp does in this article.

An overview of

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

This method looks neat at first. There are three WidgetsFlutterBinding methods called. Before we look at the implementations of the three methods, let’s take a look at what WidgetsFlutterBinding is.

WidgetsFlutterBinding

Let’s look at the official explanation:

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

Translation: a specific binding for an application based on the Widgets Framework, which is the glue layer that binds the Framework to Flutter Engine.

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance; }}Copy the code

Its parent class BindingBase is an abstract class, and with implements a number of mixins that can only be used by classes that inherit from BindingBase. The purpose of mixins is to extend functionality. Mixins can be likened to the iOS protocol.

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 - Windows support is / / active. / / the only window UI. SingletonFlutterWindow get window = > ui.window; /// Each BindingBase class defines a behavior with a platformDispatcher as a callback (handlers). ui.PlatformDispatcher.instance; Void initInstances() {assert(! _debugInitialized); assert(() { _debugInitialized = true; return true; } ()); } /// The current [WidgetsBinding], If one has been created. /// ensureInitialized returns an instance /// 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; Void initServiceExtensions() {// omit the code}}Copy the code
  • ui.windowIs:Flutter AppDisplay window, which is inherited fromFlutterView, located in theFlutter engineLayer.
  • ui.PlatformDispatcher.instanceThe platformDispatcher is a Flutter event dispatcher that distributes Flutter events to the Engine and transmits them to the Engine layer.
  • initInstances: Method to initialize an instance.
  • initServiceExtensions()Register:service extensions, such asplatformOverride,activeDevToolsServerAddressAnd so on.

The ensureInitialized () method

The purpose of this method is to return an instance of the WidgetsBinding type, or create one if not already created.

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

Return a WidgetsBinding. Instance instance, Because WidgetsFlutterBinding implements GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBindingmixin, mixin initInstances, and initServiceExtensions methods also call each mixin’s functionality:

  • GestureBinding: Handle gestures.
  • SchedulerBinding: Processes system scheduling.
  • ServicesBinding: Handles interactions with the native.
  • PaintingBinding: Handles drawing.
  • SemanticsBinding: Handles semantics.
  • RendererBinding: Handle rendering.
  • WidgetsBinding:WidgetsRelated.

Let’s focus on WidgetsBinding and RendererBinding.

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); } // omit the code}Copy the code

The WidgetsBinding initialization creates a BuildOwner object that manages the Widget tree and the Element tree.

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; // Omit the code}Copy the code

The RendererBinding initialization creates a PipelineOwner object that manages the RenderObject tree. PipelineOwner and BuildOwner both reside in the Framework layer and they interact with the Engine through the Bingding(glue layer).

  • PipelineOwner has been initialized to manage RenderObject.
  • will_handlePersistentFrameCallbackThis callback is passedSchedulerBindingIn the_postFrameCallbacksThis is done each time the hardware is issuedVSyncSignalRenderBindingIn the_handlePersistentFrameCallbackMethods._handlePersistentFrameCallbackMethoddrawFrameMethods.

scheduleAttachRootWidget

The scheduleAttachRootWidget method is called after instantiation.

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

The attachRootWidget method is called:

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(); }}Copy the code

The attachRootWidget method is used to generate a root Element for the root Widget. Generating an Element calls the attachToRenderTree method and passes in BuildOwner and Element.

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement < T > element?]) {if element (= = null) {/ / / create a new element owner. LockState (() {element = createElement(); assert(element ! = null); element! .assignOwner(owner); }); // Create the ability to update the widget tree by calling back all the elment owners.buildScope (Element! , () { element! .mount(null, null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element! ; }Copy the code

This method creates an element when the element is empty. It calls BuildOwner’s buildScope to create the ability to update the Widget tree. You can call the callback to build all the elements that are marked as dirty.

AttachRootWidget method, finally will perform 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; }}Copy the code

It mainly calls new frame scheduling management. It calls the scheduleFrame method

void scheduleFrame() { if (_hasScheduledFrame || ! framesEnabled) return; assert(() { if (debugPrintScheduleFrameStacks) debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.'); return true; } ()); / / / to window set callback ensureFrameCallbacksRegistered (); Window.scheduleframe (); _hasScheduledFrame = true; }Copy the code

This will set the onBeginFrame and onDrawFrame callbacks to the window and the window will pass the callbacks to the 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;
  }
Copy the code

That is, scheduleAttachRootWidget passes the SchedulerBinding’s _handleBeginFrame and _handleDrawFrame to the platformDispatcher after a series of calls. The platformDispatcher dispatches events from Enginee. Here SingletonFlutterWindow transfers the onBeginFrame and onDrawFrame events of the platformDispatcher to the SchedulerBinding.

OnDrawFrame of the platformDispatcher is called when the hardware emits a VSync signal. The _handleDrawFrame method in the SchedulerBinding is actually called. _handleDrawFrame calls the handleDrawFrame method:

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 {/// omit the code}}Copy the code

The _postFrameCallbacks store the callback, which is called every time the hardware sends a VSync signal. The _postFrameCallbacks here are passed in the initInstances method of the RenderBinding mixin

addPersistentFrameCallback(_handlePersistentFrameCallback);
Copy the code

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(); }); }Copy the code

The main call to this method is scheduleFrame, and the follow-up code is actually window.scheduleFrame(),

  @override
  void scheduleFrame() {
    platformDispatcher.scheduleFrame();
  }
Copy the code

Window. ScheduleFrame () call the platformDispatcher. ScheduleFrame (), notify the engine layer need to draw. The engine calls the onDrawFrame method of the platformDispatcher as quickly as possible.

conclusion

The runApp method basically does the following:

  • createWidgetsFlutterBindingIt is connectedframeworkandengineThe glue layer. registeredVsyncCallback, each subsequent frame of the call will startWidgetsFlutterBindingAnd finally passed toframeworkLayer processing logic.
  • attachRootWidget: Traverses the entire mount view tree and buildswidget,element,renderObjcectIs connected to.
  • scheduleWarmUpFrame: The dispatch frame is warmUp. Execute frame drawinghandleBeginFrameandhandleDrawFrameMethods.