preface

Widgets from the previous article, Element, RenderObject tree build and update process analysis of the Flutter of the nodes in the building, starting from the source, to explain how the node is step by step to build, in the article also mentioned WidgetsFlutterBinding, but did not elaborate.

In this article, the main analysis of the App construction process, analyze what has happened since runApp. What is a WidgetsFlutterBinding

There is also a flow chart at the back of the article, through which you can understand the startup process of APP

What does runApp do?

In our main function, we usually call the runApp(Widget app) method. So what does this method actually do? The implementation of runApp is as follows

Void runApp (Widget app) {WidgetsFlutterBinding. The ensureInitialized () / / initialization. ScheduleAttachRootWidget (app)// Bind the root widget.. scheduleWarmUpFrame(); // Make sure the first frame is as soon as possible}Copy the code

This method calls the three WidgetsFlutterBinding methods to initialize the WidgetsFlutterBinding, bind the widget, and start building the page. Let’s first analyze what this WidgetsFlutterBinding is.

The Binding of Flutter

Binding in Flutter is a glue layer that connects the framework and engine layer. The relationship between Flutter and engine layer is roughly as follows

The runApp method creates a glue layer, WidgetsFlutterBinding, and connects the framework and engine layers.

WidgetsFlutterBinding

A WidgetsFlutterBinding is an instance of a Binding, i.e. a bridge that connects the event interactions between the Engine and framework of a Flutter. The definition of WidgetsFlutterBinding is as follows

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

WidgetsFlutterBinding inherits many BindingBases. And then with a lot of mixins, and these mixins are all derived from the BindingBase class

The BindingBase class has a lot of methods. Here are some methods that are relevant to this article

ui.SingletonFlutterWindow get window => ui.window;
ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
void initInstances();
void initServiceExtensions();

Copy the code

The window is of type SingletonFlutterWindow and is located in the engine layer of a Flutter. SingletonFlutterWindow is the window loaded by our Flutter app, and SingletonFlutterWindow is a singleton inherited from FlutterView.

There’s a method in FlutterView

PlatformDispatcher get platformDispatcher;
Copy the code

This method returns a platformDispatcher, which is an event dispatcher for a Flutter. This method is responsible for the Flutter dispatch of engine events and passing events to the engine layer.

SingletonFlutterWindow inherits from FlutterView. It inherits platformDispatcher. That is, SingletonFlutterWindow uses platformDispatcher to handle events from the Flutter Engine layer. PlatformDispatcher dispatches flutter Engine events to SingletonFlutterWindow, and passes Flutter Engine events to SingletonFlutterWindow.

Notice that BindingBase also has a platformDispatcher. BindingBase is the same platformDispatcher as FlutterView.

WidgetsFlutterBinding.ensureInitialized()

Said WidgetsFlutterBinding back now, when the call in runApp WidgetsFlutterBinding. The ensureInitialized, returns an instance. If instance is empty, the default constructor is called to create an instance.

The default constructor is implemented in BindingBase as follows

BindingBase() { ... initInstances(); // instantiate... initServiceExtensions(); // Registration service... }Copy the code

Because the WidgetsFlutterBinding ends with the WidgetsBinding, WidgetsBinding is inherited BindingBase ServicesBinding SchedulerBinding, PaintingBinding, GestureBinding RendererBinding, SemanticsBinding. So the initInstances and initServiceExtensions methods of these mixins are executed as well. Both the WidgetsFlutterBinding and its with mixins instantiate and register the service together.

The mixins are responsible for the following

  • BindingBase The base class for each binding related mixin
  • ServicesBinding handles the native interaction channel
  • GestureBinding handles gestures
  • RendererBinding handles rendering
  • PaintingBinding handles drawing dependencies
  • SemanticsBinding handles semantics
  • WidgetsBinding handle widget

One of the things to notice is that

  • InitInstances in WidgetsBinding initialize a BuildOwner in the admin Widget and Element tree
  • InitInstances in RendererBinding initialize a PipelineOwner that manages the RenderObjct tree and creates a callback that is called every time a frame is refreshed

The binding layer and SingletonFlutterWindow WidgetsFlutterBinding do interactions. The Mixins in WidgetsFlutterBinding are all responsible for a piece of functionality.

The general relationship is as follows

WidgetsFlutterBinding.scheduleAttachRootWidget

The scheduleAttachRootWidget method is continued after WidgetsFlutterBinding is instantiated. To achieve the following

void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } void attachRootWidget(Widget rootWidget) { _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner! , renderViewElement as RenderObjectToWidgetElement<RenderBox>?) ; }Copy the code

In the previous article we said that the attachRootWidget method is used to generate a root Element for the root widget. Generating Element calls the attachToRenderTree method and passes in BuildOwner and Element. AttachRootWidget is implemented as follows

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); }); // This is most likely the first time the framework is ready to produce // a frame. Ensure that we are asked for one. SchedulerBinding.instance! .ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element! ; }Copy the code

This processing logic is also described in my last article, which is about building a node tree. When the element is empty, can call SchedulerBinding. The instance! .ensuRevisualUpdate (). This one is also very important, through the following process

ensureVisualUpdate() -> scheduleFrame() -> ensureFrameCallbacksRegistered()

Copy the code

Will call ensureFrameCallbacksRegistered method, ensureFrameCallbacksRegistered method is as follows

void ensureFrameCallbacksRegistered() { window.onBeginFrame ?? = _handleBeginFrame; window.onDrawFrame ?? = _handleDrawFrame; }Copy the code

After a series of callbacks, the _handleBeginFrame and _handleDrawFrame callbacks from the SchedulerBinding are passed to the Window of the WidgetsFlutterBinding class

set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
set onDrawFrame(VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
Copy the code

As you can see, the method in the window passes the callback to the platformDispatcher. That means WidgetsFlutterBinding. ScheduleAttachRootWidget this method after a series of method calls, The SchedulerBinding mixin’s _handleBeginFrame and _handleDrawFrame are eventually passed to platformDispatcher.

As mentioned earlier, the platformDispatcher dispatches events from Enginee. Here SingletonFlutterWindow hands the platformDispatcher onBeginFrame and onDrawFrame events to the SchedulerBinding.

PlatformDispatcher’s onDrawFrame is called when the hardware sends a VSync signal. As you can see from above, the _handleDrawFrame method in SchedulerBinding is actually called. HandleDrawFrame calls the handleDrawFrame method. The handleDrawFrame method is defined as follows

void handleDrawFrame() { ... _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (final FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!) ; .Copy the code

The _postFrameCallbacks contains callback, which is called every time the hardware issues a VSync signal.

This method takes all the callbacks from _postFrameCallbacks and executes the callback. The _postFrameCallbacks here are passed in the initInstances method of the RenderBinding mixin, as follows

void initInstances() { super.initInstances(); _instance = this; _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); . addPersistentFrameCallback(_handlePersistentFrameCallback); . }Copy the code

The initInstances method in the RenderBinding does two things

  1. A PipelineOwner is initialized to manage RenderObject.
  2. This callback _handlePersistentFrameCallback into SchedulerBinding _postFrameCallbacks in, So every time the hardware VSync signal will be called the _handlePersistentFrameCallback RenderBinding method. _handlePersistentFrameCallback method called directly drawFrame methods .

Because the drawFrame method is implemented in a WidgetsBinding, the drawFrame method in a WidgetsBinding is called. The following

void drawFrame() { ... if (renderViewElement ! = null) buildOwner! .buildScope(renderViewElement!) ; // Build the update subtree super.drawFrame(); buildOwner! .finalizeTree(); Tell buildOwner it's ready to update and release _inactiveElements...Copy the code

This method first calls buildOwnerOwner’s buildScope method to build and update the node tree. Then call the super.drawFrame() method to call the RederBinding drawFrame method. The following

void drawFrame() { assert(renderView ! = null); pipelineOwner.flushLayout(); / / refresh layout pipelineOwner flushCompositingBits (); // Layer update pipelineowner.flushpaint (); / / flush draw the if (sendFramesToEngine) {renderView.com positeFrame (); . / / tell the GPU rendering renderView pipelineOwner flushSemantics (); // this also sends the semantics to the OS. _firstFrameSent = true; }}Copy the code

This method is called to manage the RenderObject tree to generate new UI rendering information, pass it to the renderView, generate a Scene to SingletonFlutterWindow, and then render the new interface on the GPU

The hardware sends VSync signals each time

Something to add to the picture

  • BuildOwner is generated in the initInstances method of WidgetsnBiding
  • The PielineOwner is generated in the initInstances method of the RenderBinding
  • RenderView is generated in the initInstances method of the RenderBinding

WidgetsFlutterBinding.ensureInitialized()

Window. This method is the most important is to call scheduleFrame () method, thus invoking platformDispatcher. ScheduleFrame () method, to notify the engine layer need to draw. Engine calls the platformDispatcher onDrawFrame method as quickly as possible.

By the way, when we call setState() during development, the BuildOwner scheduleBuildFor method is called, and the scheduleBuildFor method calls onBuildScheduled, OnBuildScheduled is passed in from the WigetsBinding mixin. The _handleBuildScheduled method corresponding to WigetsBinding.

The _handleBuildScheduled method also ends up calling window.scheduleFrame() to tell engine to update the interface.

conclusion

The runApp method in general does the following

  1. Create a connection between the engine layer and the framework layer of the Flutter. After registering the Vsync callback, the WidgetsFlutterBinding method will be triggered every time a frame is called. To invoke the processing logic of the framework layer
  2. Build a node tree for the incoming widget, give the result of the RenderObjct tree in the node tree to SingletonFlutterWindow in the Enginee layer, and then notify the GPU for rendering