This is the first day of my participation in the August More text Challenge. For details, see: August More Text Challenge

The article series Flutter is serialized

  • An In-depth analysis of the Engineering Structure and Application Layer Compilation source code of Flutter Android

  • The Nature of the Flutter Command: An In-depth source analysis of the Flutter Tools mechanism

  • RunApp of Flutter and Source Analysis of the Three Tree Birth Process

  • Source Code Analysis of the Activity/Fragment process on the Android Side Flutter

background

Since writing the first line of the Flutter, we have known that the Widget you write will be passed in the Dart main method by calling the runApp method, which will compile and run as expected. Have you ever wondered what’s going on behind the scenes? Why is runApp so mysterious? In other words, you should have heard or seen a lot of the core mechanics of the three Trees of Flutter since you started. Have you really thought about what they are? If not, this is a tour de force.

Flutter program entry

The Flutter App we wrote generally enters the main method and adds and runs our own Widget for the entire application by calling the runApp method, so let’s look directly at the runApp method implementation, as follows:

/** * location: FLUTTER_SDK\packages\flutter\lib\ SRC \widgets\binding. Dart * The Widget layout for the app parameters box constraints is forced to fill the screen. This is the framework mechanism, and if you want to adjust it yourself, you can wrap it with Align and so on. * Repeated calls to runApp will remove the added App Widget from the screen and add a new one. * The framework will compare the new Widget tree with the previous Widget tree and apply any differences to the underlying rendered tree. This is similar to the rebuild mechanism after StatefulWidget calls state.setState. * /
void runApp(Widget app){ WidgetsFlutterBinding.ensureInitialized() .. scheduleAttachRootWidget(app) .. scheduleWarmUpFrame(); }Copy the code

You can see that the three lines above represent the core three steps (cascading operator calls) of Flutter initiation:

  1. WidgetsFlutterBinding initializes (ensureInitialized())
  2. Bind the root node to create three core trees (scheduleAttachRootWidget(app))
  3. Draw the warm-up frame (scheduleWarmUpFrame())

WidgetsFlutterBinding instance and initialization

Look directly at the source code, 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 from BindingBase with a large number of mixin classes. The WidgetsFlutterBinding is the core bridge that connects the Widget architecture to the Flutter Engine and is the application layer core of the entire Flutter. Using the ensureInitialized() method we can get a global singleton instance of WidgetsFlutterBinding, and a bunch of XxxBinding in the mixin are instantiated.

The constructor of the BindingBase abstract class calls the initInstances() method, and the various mixins have their own initxbinding emphasis, each with its own initxBinding responsibilities, as follows:

  • WidgetsFlutterBinding: Core bridge body, Flutter app globally unique.
  • BindingBase: Binding service abstract class.
  • GestureBinding: the GestureBinding handles screen event distribution and event callback processing. The initialization method of the GestureBinding is focused on the event processing callback_handlePointerDataPacketA property assigned to the window function so that the window can be called when it receives screen events. The Window instance is the bridge between the Framework layer and Engine layer for processing screen events.
  • SchedulerBinding: The binding class related to the scheduler Flutter, debug the operation of counting the drawing process duration when compiling the mode.
  • ServicesBinding: The System platform message listening binding class Flutter. The Platform and the Flutter layer communication related services are registered to listen for application lifecycle callbacks.
  • PaintingBinding: Rendering preheat cache binding class.
  • SemanticsBinding: Binder binding class between the semantic tree and the Flutter engine.
  • RendererBinding: The binder binding class between the render tree and the Flutter engine. The internal focus is on holding the root node of the render tree.
  • WidgetsBinding: The binder binding class between the Widget tree and the Flutter engine.

From the macro abstraction of the Flutter architecture, these XxxBinding act as a bridge binding, as follows:

RendererBinding and WidgetsBinding initInstances() ¶ initInstances() ¶ RendererBinding and WidgetsBinding initInstances();

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances(){...The BuildOwner class is used to keep track of which Widgets need to be rebuilt. It also handles other tasks for the Widget tree, such as managing inactive Widgets, triggering rebuilds in debug mode, and so on. * /
    _buildOwner = BuildOwner();
    //2. Callback method assignment, called when the first buildable element is marked dirty.buildOwner! .onBuildScheduled = _handleBuildScheduled;//3, callback method assignment, called when local configuration changes or AccessibilityFeatures change.
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; . } } mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { @overridevoid initInstances(){.../** * 4, create class to manage rendering pipeline * provide interface calls to trigger rendering. * /
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    //5, a bunch of callback listener related to window changes
    window. onMetricsChanged = handleMetricsChanged .. onTextScaleFactorChanged = handleTextScaleFactorChanged .. onPlatformBrightnessChanged = handlePlatformBrightnessChanged .. onSemanticsEnabledChanged = _handleSemanticsEnabledChanged .. onSemanticsAction = _handleSemanticsAction;//6, create a RenderView object, which is the root node of the RenderObject treeinitRenderView(); . }void initRenderView(){...//RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
    //7. Render the root object of the tree
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }
  // Define the get method for renderView, which is taken from _pipelineOwner.RootNode
  RenderView get renderView => _pipelineOwner.rootNode! as RenderView;
  // Define the set method of the renderView. InitRenderView () instantiates an assignment to _pipelineOwner.RootNode.
  set renderView(RenderView value) { assert(value ! =null); _pipelineOwner.rootNode = value; }}Copy the code

Now that we’ve got some important information based on the initialization process, remember that the RenderView in the RendererBinding is the root node of the RenderObject’s render tree. The sequence diagram for the above code looks like this:

The scheduleAttachRootWidget command is used to create an association between three core trees

The WidgetsFlutterBinding instantiation singleton first calls the scheduleAttachRootWidget(app) method, which is located in the Mixin’s WidgetsBinding class. The essence is that the attachRootWidget(rootWidget) method is executed asynchronously, which performs the entire correlation between the Flutter Widget and the Element to the RenderObject. The source code is as follows:

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
  	// Simple asynchronous quick execution, attachRootWidget asynchronous
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }

  void attachRootWidget(Widget rootWidget) {
  	RenderViewElement (renderViewElement, renderViewElement, renderViewElement)
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    //2. The bridge creates a tree of RenderObject, Element and Widget relationships. The value _renderViewElement is the value returned by the attachToRenderTree method
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      / / 3, the types of RenderObjectWithChildMixin, inherited from RenderObject, RenderObject inherited from AbstractNode.
      // _pipelineOwner.rootNode from the RendererBinding, _pipelineOwner from the PipelineOwner object instantiated by its init initInstances method.
      Each Flutter App has only one PipelineOwner instance.
      container: renderView, 
      debugShortDescription: '[root]'.//4. Dart Widget app
      child: rootWidget,
    // attach, buildOwner from buildOwner instance instantiated when WidgetsBinding is initialized, renderViewElement value is _renderViewElement itself, So the first entry is also null.).attachToRenderTree(buildOwner! , renderViewElementasRenderObjectToWidgetElement<RenderBox>?) ;if (isBootstrapFrame) {
      //6. The SchedulerBinding's scheduleFrame() method is called.
      // This essentially calls window.scheduleFrame().SchedulerBinding.instance! .ensureVisualUpdate(); }}}Copy the code

The code fragment above steps 2 and 5 need to cooperate with RenderObjectToWidgetAdapter class section view, are as follows:

/ / 1, RenderObjectToWidgetAdapter inherited from RenderObjectWidget, RenderObjectWidget inherited from the Widget
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {...//3. We write the Widget roots passed in the Dart's runApp function parameter
  final Widget? child;
  //4. Inheriting the rootNode property of the PipelineOwner object from the RenderObject, a Flutter App has only one PipelineOwner instance globally.final RenderObjectWithChildMixin<T> container; ./ / 5, rewrite the Widget the createElement method, constructed a RenderObjectToWidgetElement instance, it is inherited from the Element.
  / / Element the root node of the tree is RenderObjectToWidgetElement.
  @override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
  //6. Rewrite the Widget's createRenderObject implementation. The Container is essentially a RenderView.
  // The root of the RenderObject tree is the RenderView.
  @override
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;

  @override
  void updateRenderObject(BuildContext context, RenderObject renderObject){}/ * * * 7, the above code snippet RenderObjectToWidgetAdapter instance creation after call * owner from WidgetsBinding BuildOwner instance initialization time instantiation, element value is yourself. * this method to create the root Element (RenderObjectToWidgetElement), and the Element associated with the Widget, which create WidgetTree corresponding ElementTree. * Set the Widget associated with the root Element to new (_newWidget) if the Element has already been created. * You can see that the Element is created only once, and is reused directly after that. * /
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    / / 8, because the first instantiation RenderObjectToWidgetAdapter call attachToRenderTree before is not null, so the current process is null
    if (element == null) {
      //9, stop calling setState during code execution in lockState
      owner.lockState(() {
        //10, create an Element instance, calling the method in Step 5 of this snippee.
        / / call the createElement method RenderObjectToWidgetAdapter method builds a RenderObjectToWidgetElement instance, inherit RootRenderObjectElement, We inherit RenderObjectElement, and then Element.element = createElement(); assert(element ! =null);
        //11. Assign the owner property of the root Element to the BuildOwner instance instantiated when the WidgetsBinding was initialized.element! .assignOwner(owner); });//12. The mount RenderObject insideowner.buildScope(element! , () { element! .mount(null.null);
      });
    } else {
      //13. When the Widget tree is updated, _newWidget is assigned as new, and the element root is marked as markNeedsBuild
      element._newWidget = this;
      element.markNeedsBuild();
    }
    returnelement! ; }... }Copy the code

Look at the steps above 12 we advanced to the simple Element (RenderObjectToWidgetElement extends RootRenderObjectElement extends RenderObjectElement RenderObjectElement extends Element). The mount method of the RenderObjectElement subclass extends Element. The mount method of the RenderObjectElement subclass extends Element.

abstract class RenderObjectElement extends Element {
  / / 1, the Element tree by constructing methods RenderObjectToWidgetElement holds the tree Widget instance. (RenderObjectToWidgetAdapter).
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  //2. The Element tree is mounted to hold the RenderObject render tree instance.@override RenderObject get renderObject => _renderObject! ; RenderObject? _renderObject; @overridevoid mount(Element? parent, Object? newSlot){.../ / 3, through the widget tree (i.e. RenderObjectToWidgetAdapter) incoming call createRenderObject method Element instances themselves get RenderObject render tree.
    / / RenderObjectToWidgetAdapter. CreateRenderObject (this) returns the RenderObjectToWidgetAdapter members of the container, This is the RenderView tree root node analyzed above.
    _renderObject = widget.createRenderObject(this); . }}Copy the code

Here, too, is the conclusion for Flutter’s soul, the Three Trees:

  • The Widget tree root node is RenderObjectToWidgetAdapter (inherited from RenderObjectWidget extends Widget), The Widget tree we pass in runApp is appented to the child property of this root.
  • Element the root node of the tree is RenderObjectToWidgetElement (inherited from RootRenderObjectElement extends RenderObjectElement extends Element), By calling the RenderObjectToWidgetAdapter method to create the createElement method, The RenderObjectToWidgetAdapter when creating RenderObjectToWidgetElement by constructing parameter passing in, So _widget attribute of the Element value is RenderObjectToWidgetAdapter instance, meaning that Element in the tree _widget attribute holds the tree Widget instance. RenderObjectToWidgetAdapter.
  • The root of the RenderObject tree is the RenderView (RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>), while the Element for the mount by calling the Widget tree (RenderObjectToWidgetAdapter)createRenderObjectMethod for RenderObjectToWidgetAdapter instantiation of the incoming RenderView rendering the root node.

The sequence diagram corresponding to the above code flow is roughly as follows:In combination with the above summary, it is easy to see when the three trees were created (the magenta node in the sequence diagram). It is also easy to see that the Element is a “bridge” before the Widget and RenderObject, and it holds the roots of both inside, which are abstracted as follows:

Due to the length and the topic of this article, we focus on the birth process of the three trees, and how to draw and render the three trees together will not be expanded here, but will be analyzed in a special article later.

Warm-up frame drawing

Let’s take a look back at our runApp implementation. We still need the last scheduleWarmUpFrame() call in our implementation, which looks like this:

mixin SchedulerBinding on BindingBase {
  void scheduleWarmUpFrame(){... Timer.run(() { assert(_warmUpFrame); handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
      // Reset the timestamp to avoid the time difference between the warm-up frame and the hot-overload frame, which causes the implicit animation to skip frames.resetEpoch(); .if (hadScheduledFrame)
        scheduleFrame();
    });
	// This method locks event distribution until the end of the draw, ensuring that no new redraw is triggered during the draw.
	// That is, no events will be responded to until the end of this drawing.
    lockEvents(() async {
      awaitendOfFrame; Timeline.finishSync(); }); }}Copy the code

The essence of this code will not be expanded here, because the essence is that rendering frame submission is related to triggering. We will examine the framework layer rendering logic in detail in a later article, and then expand it. Just know that it will be drawn once it is called (without waiting for the VSYNC signal to arrive).

If you’re careful at this point, you might wonder, because when I was analyzing the attachRootWidget call, and the last line of the call is the launch frame, I’m going to call window.scheduleFrame() and wait for the system VSYNC signal to come and draw, Since the VSYNC signal triggers drawing, why not use this active warm-up frame?

Yes, it is ok not to, but the experience is not very good and will lead to the initialization of the card frame effect. Because window.scheduleFrame() is not executed until the VSYNC signal is received from the system, The Flutter app initializes by initiating a pin (also known as a warm-up frame) in order to render the UI as quickly as possible without waiting for the VSYNC signal to arrive. This reduces the VSYNC wait time by up to one.

conclusion

This is the birth process of the three trees at the end of the Flutter Dart, and we will analyze how the three trees work with each other in a special chapter, which will not be discussed here.