Analysis of Flutter Framework

Analysis of the Flutter Framework (I) — Overview and Window

Analysis of the Flutter Framework (II) — Initialization

Analysis of the Flutter Framework (iii) — Widget, Element and RenderObject

Analysis of The Flutter Framework (IV) — The Operation of the Flutter Framework

Analysis of the Flutter Framework (5) — Animation

Analysis of the Flutter Framework (VI) — Layout

Analysis of the Flutter Framework (VII) — Drawing

preface

The previous article “Flutter Framework Analysis (I) — Overview and Window” introduced the core rendering pipeline of Flutter framework and the most basic Window. In this article, we enter from the initialization of the Flutter framework to uncover the veil of Flutter step by step. Those of you who have written a Flutter application know that the entry to a Flutter app is the function runApp().

void main() {
  runApp(MyApp());
}
Copy the code

So let’s start with the function runApp() and see what happens when this function is called.

Initialize the

The function body of runApp() is in widgets/binding.dart. Long like this:

voidrunApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() .. attachRootWidget(app) .. scheduleWarmUpFrame(); }Copy the code

As you can tell from the name of the function called, it does three things,

  • Make sure thatWidgetsFlutterBindingIs initialized.
  • Post your Widget somewhere.
  • Then schedule a “warm-up” frame.

OK, let’s take a look at each of these three things.

ensureInitialized()

First let’s take a look at what a WidgetsFlutterBinding is. From the name of this class, it means to bind widgets to a Flutter.

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

This class inherits from BindingBase and is Mixin with many other classes whose names are bindings of different functions. All the static function ensureInitialized() does is return a widgetsbinding.instance singleton.

The various binding classes mixed in are also inherited from the abstract class BindingBase.

abstract class BindingBase { BindingBase() { ... initInstances(); . }... ui.Windowget window => ui.window;
}
Copy the code

There are two things you need to know about the abstract BindingBase class. One is that the function initInstances() is called when it is constructed. This function is implemented by subclasses of the mixin-bound classes, and the initialization is done internally. The other thing is that BindingBase has a getter that returns the window. Remember the Window mentioned in Flutter Framework Analysis (I) — Overview and Windows? Yes, the window here is it. Can we deduce that these bindings are actually encapsulation of Windows? Let’s take a look at what each of these binding classes does when initInstances() is called.

The first is GestureBinding. Gesture binding.

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
Copy the code

When initInstances() is called, the main thing you do is set up a gesture-handling callback for the window. So this binding is mainly responsible for managing gesture events.

The second is ServicesBinding. Service binding

mixin ServicesBinding on BindingBase {
 @override
 void initInstances() {
   super.initInstances();
   _instance = this;
   window
     ..onPlatformMessage = BinaryMessages.handlePlatformMessage;
   initLicenses();
 }
Copy the code

This binding basically sets the window to handle Platform Message callbacks.

The third is SchedulerBinding. Schedule binding.

mixin SchedulerBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  window.onBeginFrame = _handleBeginFrame;
  window.onDrawFrame = _handleDrawFrame;
  SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
}
Copy the code

This binding mainly sets the onBeginFrame and onDrawFrame callbacks to the window. Recall from our last article on the rendering process that engine calls back to the Flutter when the Vsync signal arrives to start the rendering process. These two callbacks are managed in the SchedulerBinding.

The fourth is PaintingBinding. Draw the binding.

mixin PaintingBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _imageCache = createImageCache();
}
Copy the code

This binding just creates an image cache without going into details.

The fifth is SemanticsBinding. Auxiliary function binding.

mixin SemanticsBinding on BindingBase {
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _accessibilityFeatures = window.accessibilityFeatures;
}
Copy the code

I won’t go into details about the binding management assist.

The sixth is RendererBinding. Render bindings. This is an important class.

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);
   _mouseTracker = _createMouseTracker();
 }

Copy the code

This binding is responsible for managing the rendering flow and doing a lot of initialization. The PipelineOwner class is instantiated first. This class is responsible for managing and driving the rendering pipeline we talked about earlier. We then set up a series of callback functions for the Window to handle screen size changes, brightness changes, and so on. InitRenderView () is then called.

  void initRenderView() {
   assert(renderView == null);
   renderView = RenderView(configuration: createViewConfiguration(), window: window);
   renderView.scheduleInitialFrame();
 }
Copy the code

This function instantiates a RenderView class. RenderView inherits from RenderObject. We all know that there is a render tree in the Flutter framework. The RenderView is the root of the Render Tree. This can be seen by opening the “Flutter Inspector”. Under the “Render Tree” Tab, the RenderView is displayed in the red box at the root.

The last call addPersistentFrameCallback added a callback function. Remember this callback, where the main phase of the rendering pipeline starts.

The seventh is WidgetsBinding, the component binding.

mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }
Copy the code

BuildOwner is initialized with an onBuildScheduled callback. Remember how the render binding instantiated PipelineOwner when initialized? The BuildOwner is instantiated in the component binding. It is responsible for managing the rebuild of widgets, keeping these two “owners” in mind. They will be the core classes in the Flutter framework. I then set two callbacks to the window, which I won’t go into because it doesn’t have much to do with rendering. The last set SystemChannels. Navigation and SystemChannels. The message processing function of the system. One of these callbacks deals specifically with routing, and the other deals with system events such as clipboard, vibration feedback, system sound, and so on.

At this point, WidgetsFlutterBinding. The ensureInitialized () ran out, generally speaking is provided in the Windows API respectively encapsulated into the different Binding. The SchedulerBinding, RendererBinding and WidgetsBinding are the ones we need to focus on. These three are important parts of the rendering pipeline.

It’s time to look at the second call in runApp().

attachRootWidget(app)

The code for this function is as follows:

  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget
    ).attachToRenderTree(buildOwner, renderViewElement);
  }
Copy the code

When we initialize the RendererBinding, we get an instance of the RenderView, the root of the Render Tree. The RenderView is inherited from the RenderObject, which needs to have corresponding widgets and elements. In the above code RenderObjectToWidgetAdapter is the Widget. And the corresponding Element is RenderObjectToWidgetElement, now that is associated with the render tree root node, it natural Element is the root node of the tree.

From the above analysis, we can draw the conclusion that:

  • Render binding (RendererBinding) bypipelineOwnerIndirectly holds the root node of the Render TreeRenderView.
  • Component binding (WidgetsBinding) holds the root node of the Element TreeRenderObjectToWidgetElement.

How and RenderView RenderObjectToWidgetElement is associated, the nature is done through a Widget, see RenderObjectToWidgetAdapter code:

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  /// Creates a bridge from a [RenderObject] to an [Element] tree.
  ///
  /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
  RenderObjectToWidgetAdapter({
    this.child,
    this.container,
    this.debugShortDescription
  }) : super(key: GlobalObjectKey(container));

  @override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

  @overrideRenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container; . }Copy the code

You see, the createElement method is RenderObjectToWidgetElement () returns, and createRenderObject returned the container is to construct the Widget incoming RenderView. And our own MyApp as a child widgets exists in RenderObjectToWidgetAdapter.

The last attachToRenderTree call does what we described earlier as the Build phase of the render pipeline, which generates the Element Tree and RenderTree based on our widgets. After the Build phase is complete, it’s natural to move on to the Layout phase and the Paint phase. How do I get in? That’s the last function call in runApp.

scheduleWarmUpFrame()

  void scheduleWarmUpFrame() {
    ...
    Timer.run(() {
      ...
      handleBeginFrame(null); . }); Timer.run(() { ... handleDrawFrame(); . }); }Copy the code

So this function actually calls two functions, the same two callbacks that we talked about when we were talking about window onBeginFrame and onDrawFrame? This is where the two callbacks are executed. The first frame is rendered and sent to engine for display on screen. Timer.run() is used to run the two callbacks asynchronously to give them a chance to finish processing the microtask queue before they are called. For information about asynchronous Dart code execution, see my article “Asynchronism in Flutter/Dart”.

We said earlier that the rendering pipeline is driven by the Vsync signal, but all of this is done in runApp(). I don’t see where engine is told to schedule a frame. This is because we are doing Flutter initialization. To save time waiting for the Vsync signal, I ran the rendering process straight to the first frame.

conclusion

This concludes the initialization of the Flutter framework. It also includes an overview of how to render the first frame of the Flutter app. The main points of the Flutter framework initialization process mentioned in this paper are the initialization of several bindings. Keep in mind the rendering pipeline and Windows introduced in the previous article. The Flutter framework actually works around these two things. The main points of this article are summarized as follows:

  • Three important bindings:SchedulerBinding.RendererBindingandWidgetsBinding.
  • 2 “owner” :PipelineOwnerandBuildOwner.
  • Render tree root: render tree rootRenderView; Element Tree Root nodeRenderObjectToWidgetElement.

With these foundations in mind, subsequent articles will analyze the relationships between widgets, Elements and RenderObjects, as well as how specific stages of the Flutter rendering pipeline work.