One, foreword

Flutter is a new, responsive, cross-platform mobile development framework. More and more developers are learning or researching Flutter to build high-performance applications on iOS and Android platforms using a single set of code. When I first came to FlutterFlutter Chinese, I saw this sentence: Widgets are the basic building blocks of the USER interface of Flutter application. Each Widget is an immutable declaration that is part of the user interface. Unlike other frameworks that separate views, controllers, layouts, and other properties, Flutter has a consistent unified object model: widgets. During development, widgets can be defined as buttons, styles, Padding, layouts, GestureDetector, etc. At first, I thought the Widget was just a view in my eyes, but it wasn’t. Here’s the story.

2. View basics

1.Widget

The introduction to Widgets on the official Website of Flutter begins with the following sentence:

Flutter widgets are built using a modern react-style framework, which takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

The Flutter widgets are built using the React idea and a responsive framework. The idea is to use widgets to build UI(interfaces). Widgets describe their view based on their current configuration and state. When the state of a widget changes, the widget reconstructs the view described, and the framework differentiates the views described earlier (when the state has not changed) to determine the minimum change steps required for the underlying rendering tree to transition from one state to the next.

The Widget definition in the Flutter developer documentation is as follows:

Describes the configuration for an Element.

Widgets are the central class hierarchy in the Flutter framework. A widget is an immutable description of part of a user interface. Widgets can be inflated into elements, which manage the underlying render tree.

The widget provides configuration information for the Element (described below), so you can see that the widget is related to the Element in some way. Widgets are central class hierarchies within the Flutter framework. Widgets are immutable objects and part of the interface. Widgets are rendered on Elements and manage the underlying render tree. The message here is that the widget will eventually be converted to an Element during rendering. Read on:

Widgets themselves have no mutable state (all their fields must be final). If you wish to associate mutable state with a widget, consider using a StatefulWidget, which creates a State object (via StatefulWidget.createState) whenever it is inflated into an element and incorporated into the tree.

Wigets itself has no mutable state (all its fields must be final). If you want mutable State and a widget, you can use StatefulWidget, StatefulWidget by using StatefulWidget. CreateState method to create the State object, and expand to the element and merge into the tree. The takeaway from this paragraph is that the widget does not render and manage State directly; managing State is left to the State object. Read on:

A given widget can be included in the tree zero or more times. In particular a given widget can be placed in the tree multiple times. Each time a widget is placed in the tree, it is inflated into an Element, which means a widget that is incorporated into the tree multiple times will be inflated multiple times.

A given widget can be included in the tree zero or more times. A given widget can be placed in the tree multiple times. Each time a widget is placed in the tree, it is expanded to an Element, which means that a widget merged into the tree multiple times will be expanded to the corresponding Element. In an interface where multiple Text objects are mounted on the view tree, the Text widgets are populated into their own separate Elements, creating multiple Element objects even if the widgets are reused. Read on:

The key property controls how one widget replaces another widget in the tree. If the runtimeType and key properties of the two widgets are operator==, respectively, then the new widget replaces the old widget by updating the underlying element (i.e., by calling Element.update with the new widget). Otherwise, the old element is removed from the tree, the new widget is inflated into an element, and the new element is inserted into the tree.

Each widget has its own unique key, and it’s easy to understand that the key is the unique identifier. The key property controls how one widget replaces another widget in the tree. If the runtimeType and key attributes of the two widgets are equal ==, the new widget replaces the old widget by updating Element(call element.update with the new widget). Otherwise, if the runtimeType and key attributes of the two widgets are not equal, the old Element will be removed from the tree, and the new widget will be expanded into a new Element, which will be inserted into the tree. If moving or deleting a widget is involved, the widget runtime and key are compared.

To sum up:

  • The widget provides configuration information (data) to the Element. The widget tree constructed by the interface is really just a configuration information tree, in order to construct the Element tree.
  • An Element corresponds to a widget because it is generated from a widget.
  • The same widget can create more than one element, meaning that one widget object can correspond to more than one Element object.

Take a look at the widget source code:

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  // omit comments
  final Key key;

  // Build element
  @protected
  Element createElement(a);

  // A short text description of the widget
  @override
  String toStringShort(a) {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }
  
  // Debug the diagnostic tree information
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }


  The static method, as explained in the previous section, is whether to update the configuration of the old UI rendering tree with the new widget object
  // If the runtimeType and key of oldWidget and newWidget are equal, the newWidget object is used to update the corresponding Element information
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    returnoldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }}Copy the code

Note also that widgets are abstract classes. In normal times, we continue with statelessWidgets and StatefulWidgets. These classes are also inherited widgets, and they must implement the createElement method.

StatelessWidget
abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key); .@override
  StatelessElement createElement(a) => StatelessElement(this); . } StatefulWidgetabstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key key }) : super(key: key); .@override
  StatefulElement createElement(a) => StatefulElement(this); . }Copy the code

As you can see, both elements are created, but the Type of Element created by StatelessWidget and StatefulWidget is different, so I won’t go into that here. The widget has a relationship with element, so look at Element.

2.Element

Take a look at the opening page of the official developer documentation:

An instantiation of a Widget at a particular location in the tree.

An element is an instance of a widget at a specific location in the tree. It’s pretty obvious here that the Widget is the director, deploying the technical planning, and Element is the employee, doing the real work. Read on:

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

It means: Widgets describe how to configure subtrees. Since widgets are immutable, multiple subtrees can be configured using the same widget at the same time. Element represents an instance of a specific location in the widget configuration tree. For example, if the parent widget is rebuilt and a new widget is created for this location.

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

Elements form a tree, and most Elements have a single child, but some widgets(like RenderObjectElement) can have multiple children.

Elements have the following lifecycle:

  • The framework creates an element by calling Widget.createElement on the widget that will be used as the element’s initial configuration.
  • The framework calls mount to add the newly created element to the tree at a given slot in a given parent. The mount method is responsible for inflating any child widgets and calling attachRenderObject as necessary to attach any associated render objects to the render tree.
  • At this point, the element is considered “active” and might appear on screen.
  • At some point, the parent might decide to change the widget used to configure this element, for example because the parent rebuilt with new state. When this happens, the framework will call update with the new widget. The new widget will always have the same runtimeType and key as old widget. If the parent wishes to change the runtimeType or key of the widget at this location in the tree, can do so by unmounting this element and inflating the new widget at this location.
  • At some point, an ancestor might decide to remove this element (or an intermediate ancestor) from the tree, which the ancestor does by calling deactivateChild on itself. Deactivating the intermediate ancestor will remove that element’s render object from the render tree and add this element to the owner’s list of inactive elements, causing the framework to call deactivate on this element.
  • At this point, the element is considered “inactive” and will not appear on screen. An element can remain in the inactive state only until the end of the current animation frame. At the end of the animation frame, any elements that are still inactive will be unmounted. *If the element gets reincorporated into the tree (e.g., because it or one of its ancestors has a global key that is reused), the framework will remove the element from the owner’s list of inactive elements, call activate on the element, and reattach the element’s render object to the render tree. (At this point, the element is again considered “active” and might appear on screen.)
  • If the element does not get reincorporated into the tree by the end of the current animation frame, the framework will call unmount on the element. At this point, the element is considered “defunct” and will not be incorporated into the tree in the future.
  • At this point, the element is considered “defunct” and will not be incorporated into the tree in the future.

Element has the following life cycle:

  • The framework is going to do it by callingelementOf the initial configuration informationWidgettheWidget.createElementMethod to create oneelement.
  • The framework calls the mount method to add the newly created Element to the tree at the given slot in the given parent level. The mount method is responsible for putting any childWidgetExpand toWidgetAnd call it as neededattachRenderObjectTo attach any associated render objects to the render tree.
  • At this point,elementIs seen as aThe activation“May appear on the screen.
  • In some cases, the parent may change to be used to configure thisElementFor example, because the parent recreates the new state. When this happens, the framework calls a new oneWidgetUpdate method of. newWidgetWill always have with the oldWidgetThe sameruntimeTypeandkeyProperties. If the parent wants to change at this location in the treeWidgettheruntimeTypeorkeyYou can use unmounting to expand the Element in this locationWidgetTo implement.
  • At some point, an ancestor (Element) may decide to remove it from the treeelement(or intermediate ancestor), the ancestor itself through the calldeactivateChildTo complete the operation. Disabling the intermediate ancestor removes it from the render treeelementRender the object and place thiselementAdded to the list of inactive elements in the owner property, which the framework callsdeactivateThat’s what the method doeselementOn.
  • At this point, theelementIs deemed “invalid” and will not appear on screen. aelementThe “inactive” state can be saved until the end of the animation frame. When the animation frame ends, all that are still inactive are unloadedelement.
  • ifelementTo be overwritten into a tree (for example, because it or one of its ancestors has a global key reused), the framework will be inactive from the ownerelementsRemove this from the listelementAnd calls theelementtheActivate methodAnd re-attach toelementTo the render object on the render tree. (At this point, the element is again considered “active” and may appear on the screen)
  • ifelementIf the element is not regrouped at the end of the current animation frame (the last frame), the framework will call itUnmount to method.

You can see the life cycle of an Element here. This means that The Flutter has helped us map the actions on the widget to the Element. What I have imagined here is something to reduce the complexity of development. Let’s use an example (draw Text) to see if element is the last rendered view:

new Text("hello flutter");
Copy the code

New Text:

.@override
  Widget build(BuildContext context) {
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
    TextStyle effectiveTextStyle = style;
    if (style == null || style.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
    if (MediaQuery.boldTextOverride(context))
      effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold)); Widget result = RichText(---->Text) defaultTextStyle.textAlign ?? TextAlign.start, textDirection: textDirection,// RichText uses Directionality.of to obtain a default if this is null.
      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is nullsoftWrap: softWrap ?? defaultTextStyle.softWrap, overflow: overflow ?? defaultTextStyle.overflow, textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context), maxLines: maxLines ?? defaultTextStyle.maxLines, strutStyle: strutStyle, text: TextSpan( style: effectiveTextStyle, text: data, children: textSpan ! =null ? <TextSpan>[textSpan] : null,),); .Copy the code

Continue with RichText:

.@override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection ! =null || debugCheckHasDirectionality(context));
    // Returns the RenderParagraph Widget
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      locale: locale ?? Localizations.localeOf(context, nullOk: true)); }...Copy the code

You see that it ends up returning RenderParagraphwidget, so keep going:

class RichText extends LeafRenderObjectWidget {}Copy the code

RichText inherits LeafRenderObjectWidget.

RenderObjectWidgets: {RenderObjectWidgets: {RenderObjectWidgets: {RenderObjectWidgets:}}}
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
  const LeafRenderObjectWidget({ Key key }) : super(key: key);
  
  // Create LeafRenderObjectElement
  @override
  LeafRenderObjectElement createElement(a) => LeafRenderObjectElement(this);
}
Copy the code

RenderObjectWidget is an abstract class that inherits RenderObjectWidget.

/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
RenderObjectWidgets provides configuration information to [RenderObjectElement], wraps [RenderObject], and provides the actual rendering in the application
abstract class RenderObjectWidget extends Widget {

  const RenderObjectWidget({ Key key }) : super(key: key);
  
  / / create the element
  @override
  RenderObjectElement createElement(a);
  / / create a RenderObject
  @protected
  RenderObject createRenderObject(BuildContext context);

  / / update the RenderObject
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {}/ / uninstall RenderObject
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) {}}Copy the code

The RenderObject is the real object behind the drawing UI, so here’s a simple trace:

3.RenderObjectWidget

RenderObjectWidget (RenderObjectWidget)

3.1. SingleChildRenderObjectWidget

RenderObjectWidgets
RenderObject
widget

3.2. MultiChildRenderObjectWidget

RenderObjectWidgets
children
RenderObject

3.3. LeafRenderObjectWidget

RenderObjectWidgets
RenderObject

  • SingleChildRenderObjectWidgetUsed with only one childwidget.
  • MultiChildRenderObjectWidgetUsed of having childrenwidget.
  • LeafRenderObjectWidgetUsed without a childwidget.
  • RenderObjectWidgetDefines methods for creating, updating, and deleting renderObjects, which subclasses must implement,RenderObjectIs the actual object for the final layout, UI rendering.

So if I’m on the screen, let’s say the layout looks like this:

Element
Widget
RendObject
Element
RendObject

  • As low as possibleRenderObjectTree changes to improve page rendering efficiency.
  • Without directly manipulating the UI, the code is easier to understand and concise through data-driven views.

The UI tree is composed of element nodes, but the final rendering is done by RenderObject. The process of creating, displaying and displaying a widget is as follows: The widget generates an Element, then uses the createRenderObject method to create a corresponding RenderObject associated with the Element. RenderObject, and then the RenderObject is used to complete the drawing.

Three, start to display

Now that we know how a widget is rendered on the screen, let’s start with the main() method entry and see how an app runs from click start to run:

1.WidgetsFlutterBinding.ensureInitialized

/ / app entrance
void main(a) => runApp(MyApp());
Copy the code

The app entry function calls the runApp method. Let’s see what happens in the runApp method:

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

First parameter (Widget app) is a Widget, one line below the line of analysis, into * * WidgetsFlutterBinding. The ensureInitialized method () * * :

/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
// Meaning: the specific binding of an application based on the Widget Framework
// This is a bridge to bind the Framework Widget to the Flutter engine
class WidgetsFlutterBinding extends BindingBase with GestureBinding.ServicesBinding.SchedulerBinding.PaintingBinding.SemanticsBinding.RendererBinding.WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  [WidgetsFlutterBinding] ¶ If a WidgetsFlutterBinding has been created, then it is a [WidgetsFlutterBinding]. If a WidgetsFlutterBinding has been created, then at least [WidgetsBinding] is implemented.
  If you only want to call this method, then you need to bind and initialize it before calling runApp
  static WidgetsBinding ensureInitialized(a) {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    returnWidgetsBinding.instance; }}Copy the code

See this WidgetsFlutterBinding mixed with (with) a lot of Binding, let’s look at the parent class BindingBase:

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    assert(! _debugInitialized); initInstances();assert(_debugInitialized);

    assert(! _debugServiceExtensionsRegistered); initServiceExtensions();assert(_debugServiceExtensionsRegistered);

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});

    developer.Timeline.finishSync();
  }

  static bool _debugInitialized = false;
  static bool _debugServiceExtensionsRegistered = false;
  ui.Window get window => ui.window;// Get the window instance
  @protected
  @mustCallSuper
  void initInstances(a) {
    assert(! _debugInitialized);assert(() { _debugInitialized = true; return true; }());
  }
}
Copy the code

UI.Window get Window => UI.Window; In Android, all views are rendered by Window. There are also Windows in Flutter. Look at the role of Window in Flutter and see what it is officially defined as:

class Window {
  Window._();

  // Return DPI, which is the number of pixels per inch and is the firmware property of the device screen
  // The fetch may be inaccurate
  double get devicePixelRatio => _devicePixelRatio;
  double _devicePixelRatio = 1.0;

  // The size of the area to draw the UI
  Size get physicalSize => _physicalSize;
  Size _physicalSize = Size.zero;

  // Get the physical pixels of the rectangle
  WindowPadding get viewInsets => _viewInsets;
  WindowPadding _viewInsets = WindowPadding.zero;

  // Get the inner margin
  WindowPadding get padding => _padding;
  WindowPadding _padding = WindowPadding.zero;

  // Triggered when the drawing area changes
  VoidCallback get onMetricsChanged => _onMetricsChanged;
  VoidCallback _onMetricsChanged;
  Zone _onMetricsChangedZone;
  set onMetricsChanged(VoidCallback callback) {
    _onMetricsChanged = callback;
    _onMetricsChangedZone = Zone.current;
  }

  // Triggers a callback when the system language changes
  Locale get locale {
    if(_locales ! =null && _locales.isNotEmpty) {
      return _locales.first;
    }
    return null;
  }

  // Get the system language
  List<Locale> get locales => _locales;
  List<Locale> _locales;

  // Trigger a callback when Local changes
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  VoidCallback _onLocaleChanged;
  Zone _onLocaleChangedZone;
  set onLocaleChanged(VoidCallback callback) {
    _onLocaleChanged = callback;
    _onLocaleChangedZone = Zone.current;
  }

  // Triggers a callback when the system font size changes
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  VoidCallback _onTextScaleFactorChanged;
  Zone _onTextScaleFactorChangedZone;
  set onTextScaleFactorChanged(VoidCallback callback) {
    _onTextScaleFactorChanged = callback;
    _onTextScaleFactorChangedZone = Zone.current;
  }

  // Trigger a callback when the screen brightness changes
  VoidCallback get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
  VoidCallback _onPlatformBrightnessChanged;
  Zone _onPlatformBrightnessChangedZone;
  set onPlatformBrightnessChanged(VoidCallback callback) {
    _onPlatformBrightnessChanged = callback;
    _onPlatformBrightnessChangedZone = Zone.current;
  }

  // Callback when the screen refreshes
  FrameCallback get onBeginFrame => _onBeginFrame;
  FrameCallback _onBeginFrame;
  Zone _onBeginFrameZone;
  set onBeginFrame(FrameCallback callback) {
    _onBeginFrame = callback;
    _onBeginFrameZone = Zone.current;
  }

  // Callback when drawing the screen
  VoidCallback get onDrawFrame => _onDrawFrame;
  VoidCallback _onDrawFrame;
  Zone _onDrawFrameZone;
  set onDrawFrame(VoidCallback callback) {
    _onDrawFrame = callback;
    _onDrawFrameZone = Zone.current;
  }

  // The click or pointer event triggers the callback
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  PointerDataPacketCallback _onPointerDataPacket;
  Zone _onPointerDataPacketZone;
  set onPointerDataPacket(PointerDataPacketCallback callback) {
    _onPointerDataPacket = callback;
    _onPointerDataPacketZone = Zone.current;
  }

  // Get the default route for the request
  String get defaultRouteName => _defaultRouteName();
  String _defaultRouteName(a) native 'Window_defaultRouteName';

  // After this method is called, onBeginFrame and onDrawFrame are immediately attached and will be called when appropriate
  void scheduleFrame(a) native 'Window_scheduleFrame';

  // Update the rendering applied to the GPU directly by calling the Window_render method of the Flutter Engine
  void render(Scene scene) native 'Window_render';

  // Whether the semantic content of the window changes
  bool get semanticsEnabled => _semanticsEnabled;
  bool _semanticsEnabled = false;

  // Callback when the window language changes
  VoidCallback get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
  VoidCallback _onSemanticsEnabledChanged;
  Zone _onSemanticsEnabledChangedZone;
  set onSemanticsEnabledChanged(VoidCallback callback) {
    _onSemanticsEnabledChanged = callback;
    _onSemanticsEnabledChangedZone = Zone.current;
  }

  // Callback when user expresses write action
  SemanticsActionCallback get onSemanticsAction => _onSemanticsAction;
  SemanticsActionCallback _onSemanticsAction;
  Zone _onSemanticsActionZone;
  set onSemanticsAction(SemanticsActionCallback callback) {
    _onSemanticsAction = callback;
    _onSemanticsActionZone = Zone.current;
  }

  // Enable other helper function callbacks
  VoidCallback get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
  VoidCallback _onAccessibilityFeaturesChanged;
  Zone _onAccessibilityFlagsChangedZone;
  set onAccessibilityFeaturesChanged(VoidCallback callback) {
    _onAccessibilityFeaturesChanged = callback;
    _onAccessibilityFlagsChangedZone = Zone.current;
  }

  // Update the semantic data for this window
  void updateSemantics(SemanticsUpdate update) native 'Window_updateSemantics';

  // Set the Isolate debug name
  void setIsolateDebugName(String name) native 'Window_setIsolateDebugName';

  // Send a message to a particular platform
  void sendPlatformMessage(String name, ByteData data, PlatformMessageResponseCallback callback) {
    final String error =
        _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
    if(error ! =null)
      throw new Exception(error);
  }
  String _sendPlatformMessage(String name, PlatformMessageResponseCallback callback, ByteData data) native 'Window_sendPlatformMessage';

  // Get the platform message callback
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  PlatformMessageCallback _onPlatformMessage;
  Zone _onPlatformMessageZone;
  set onPlatformMessage(PlatformMessageCallback callback) {
    _onPlatformMessage = callback;
    _onPlatformMessageZone = Zone.current;
  }

  // called by _dispatchPlatformMessage
  void _respondToPlatformMessage(int responseId, ByteData data)
      native 'Window_respondToPlatformMessage';

  // Platform information responds to a callback
  static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
    if (callback == null)
      return null;

    // Store the zone in which the callback is being registered.
    final Zone registrationZone = Zone.current;

    return(ByteData data) { registrationZone.runUnaryGuarded(callback, data); }; }}Copy the code

You can see that the window contains some information and callbacks for the current device system, so now look at the various bindings mixed in:

  • GestureBinding: Binding gesture subsystem, providedonPointerDataPacketThe callback.
  • ServicesBinding: Binding platform message channels, providedonPlatformMessageThe callback.
  • SchedulerBinding: Binds the draw scheduling subsystem, providedonBeginFrameandonDrawFrameThe callback.
  • PaintingBinding: Binds the drawing library to handle the image cache.
  • SemanticsBinding: Bridge the semantic layer to the Flutter engine, providing low-level support for ancillary functions.
  • RendererBinding: provides a bridge between the rendering tree and the Flutter engineonMetricsChangedandonTextScaleFactorChangedThe callback.
  • WidgetsBinding: provides a bridge between the Flutter widget and engineonLocaleChanged.onBuildScheduleThe callback.

Namely WidgetsFlutterBinding. The ensureInitialized () this line of code name is WidgetsFlutterBinding instance initialization. In fact, it is not that simple. In addition, it obtains some basic system information and initializes some events that listen to the Window object, and then wraps, abstracts and distributes these events according to the rules of the upper Framework model. In short, WidgetsFlutterBinding is a bridge between the Flutter engine and the Framework.

2.attachRootWidget(app)

The second line is attachRootWidget(app), which means mount the root RootWidget. Click inside to see:

  // If the widget needs to be created, attach it to [renderViewElement]
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement);
  }
Copy the code

RenderView is the root node of the UI rendering tree:

  // The render tree that's attached to the output surface.
  // Mount to the root of the render tree rootNode
  RenderView get renderView => _pipelineOwner.rootNode;
Copy the code

RootWidget is the rootWidget that is passed in from the outside. Ignore it here. AttachToRenderTree (buildOwner, renderViewElement) is the method that builds the RenderTree. This method takes two arguments, starting with the buildOwner parameter:

  /// The [BuildOwner] in charge of executing the build pipeline for the
  /// widget tree rooted at this binding.
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();
Copy the code

See the official website for its definition:

And trigger the “reassemble” command when necessary during hot overloading during debugging
renderViewElement

  /// The [Element] that is at the root of the hierarchy (and which wraps the
  /// [RenderView] object at the root of the rendering hierarchy).
  ///
  /// This is initialized the first time [runApp] is called.
  Element get renderViewElement => _renderViewElement;
  Element _renderViewElement;

Copy the code

RenderViewElement is the corresponding Element of the renderView. Since renderView is the root of the tree, renderViewElement is located at the root of the hierarchy.

  /// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will have an update scheduled to switch to this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
  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);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }
Copy the code

If the root element is not created, call createElement to create the root element and associate the element with the widget. We then call Element.assigNowner (owner), which essentially sets the trace for the element, and finally call owner.buildScope, which determines the scope to update the widget. If an Element is already created, set the root Element and associated widget to new, and rebuild the element for later reuse.

3.scheduleWarmUpFrame

The last line of the runApp() method executes the scheduleWarmUpFrame method:

  /// Schedule a frame to run as soon as possible, rather than waiting for
  /// the engine to request a frame in response to a system "Vsync" signal.
  ///
  /// This is used during application startup so that the first frame (which is
  /// likely to be quite expensive) gets a few extra milliseconds to run.
  ///
  /// Locks events dispatching until the scheduled frame has completed.
  ///
  /// If a frame has already been scheduled with [scheduleFrame] or
  /// [scheduleForcedFrame], this call may delay that frame.
  ///
  /// If any scheduled frame has already begun or if another
  /// [scheduleWarmUpFrame] was already called, this call will be ignored.
  ///
  /// Prefer [scheduleFrame] to update the display in normal operation.
  void scheduleWarmUpFrame(a) {
    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); --->1
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame(); ---->2
      // 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 handleBeginFrame and handleDrawFrame methods are mainly implemented in SchedulerBinding:

3.1. HandleBeginFrame

  void handleBeginFrame(Duration rawTimeStamp) {...try {
      // TRANSIENT FRAME CALLBACKS
      Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if(! _removedIds.contains(id)) _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack); }); _removedIds.clear(); }finally{ _schedulerPhase = SchedulerPhase.midFrameMicrotasks; }}Copy the code

As you can see, this is the transientCallbacks queue, a collection of temporary callbacks that hold animation callbacks.

3.2. HandleDrawFrame

  void handleDrawFrame(a) {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS ----->
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS ------>
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      profile(() {
        _profileFrameStopwatch.stop();
        _profileFramePostEvent();
      });
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('▀' * _debugBanner.length);
        _debugBanner = null;
        return true; } ()); _currentFrameTimeStamp =null; }}Copy the code

You can see the persistentCallbacks queue being executed. This queue is used to hold persistentCallbacks. New frames cannot be drawn during such callbacks, and persistentCallbacks cannot be removed once registered. The postFrameCallbacks queue will only be called once at the end of each Frame(once drawn) and then removed by the system.

ScheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame: scheduleWarmUpFrame It says:

RendererBinding: A bridge between the rendering tree and the Flutter engine, providing onMetricsChanged and onTextScaleFactorChanged callbacks

Flutter is actually rendered and drawn within this binding:

4. Render and draw

  void initInstances(a) {
    super.initInstances();
    _instance = this;/ / initialization
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    // Add setup listenerwindow .. onMetricsChanged = handleMetricsChanged .. onTextScaleFactorChanged = handleTextScaleFactorChanged .. onPlatformBrightnessChanged = handlePlatformBrightnessChanged .. onSemanticsEnabledChanged = _handleSemanticsEnabledChanged .. onSemanticsAction = _handleSemanticsAction; initRenderView(); _handleSemanticsEnabledChanged();assert(renderView ! =null);
    / / add persistentFrameCallback
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    // Create touch management
    _mouseTracker = _createMouseTracker();
  }
Copy the code

AddPersistentFrameCallback this method mainly to persistentFrameCallback add a callback:

  void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }
Copy the code

What did see _handlePersistentFrameCallback this callback:

  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
Copy the code
  @protected
  void drawFrame(a) {
    assert(renderView ! =null);
    pipelineOwner.flushLayout();// Update the layout information
    pipelineOwner.flushCompositingBits();FlushLayout is called only after flushLayout and before flushPaint to update the RenderObject
    pipelineOwner.flushPaint();// Update the RenderObject
    renderView.compositeFrame(); // Send bit data to GPU
    pipelineOwner.flushSemantics(); // Send semantic data to the operating system
  }
Copy the code

The following method goes one by one:

4.1. FlushLayout

  void flushLayout(a) {
    profile(() {
      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
    });
    assert(() {
      _debugDoingLayout = true;
      return true; } ());try {
      // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for(RenderObject node in dirtyNodes.. sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {if (node._needsLayout && node.owner == this) node._layoutWithoutResize(); }}}finally {
      assert(() {
        _debugDoingLayout = false;
        return true;
      }());
      profile(() {
        Timeline.finishSync();
      });
    }
  }
Copy the code

Get the layout information for which renderObjects are marked dirty first, then go to ode._layoutwithOutreSize (); Readjust these RenderObjects.

4.2. FlushCompositingBits

  void flushCompositingBits(a) {
    profile(() { Timeline.startSync('Compositing bits'); });
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    profile(() { Timeline.finishSync(); });
  }
Copy the code

Check if the RenderObject needs to be redrawn and pass Node._updatecompositingbits (); Update the _needsCompositing property, redraw it if true, otherwise not.

4.3. FlushPaint

  void flushPaint(a) {
    profile(() { Timeline.startSync('Paint', arguments: timelineWhitelistArguments); });
    assert(() {
      _debugDoingPaint = true;
      return true; } ());try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      // Sort the dirty nodes in reverse order (deepest first).
      // Direction traverses the marked nodes
      for(RenderObject node in dirtyNodes.. sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {assert(node._layer ! =null);
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            // Redraw
            PaintingContext.repaintCompositedChild(node);
          } else{ node._skippedPaintingOnLayer(); }}}assert(_nodesNeedingPaint.isEmpty);
    } finally {
      assert(() {
        _debugDoingPaint = false;
        return true;
      }());
      profile(() { Timeline.finishSync(); });
    }
  }

Copy the code

This method through a reverse traversal (dirty mark) need to redraw RenderObject, finally through PaintingContext. RepaintCompositedChild (node); Redraw.

4.4.com positeFrame

  void compositeFrame(a) {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      // Create Scene object
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      // Use the render method to display the Scene object on the screen
      _window.render(scene);Call the render API of the Flutter Engine
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true; } ()); }finally{ Timeline.finishSync(); }}Copy the code

This method passes the Scene object drawn on Canvas to the window.render() method, which directly sends the Scene information to the Flutter engine. Eventually, the Flutter Engine draws the image onto the device screen. This completes the drawing process.

Note that RendererBinding is only mixed into the object, and eventually into the WidgetsBinding, back to the beginning:

class WidgetsFlutterBinding extends BindingBase with GestureBinding.ServicesBinding.SchedulerBinding.PaintingBinding.SemanticsBinding.RendererBinding.WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized(a) {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    returnWidgetsBinding.instance; }}Copy the code

So a WidgetsBinding should be used to override the drawFrame method:

  @override
  void drawFrame(a) {
    assert(! debugBuildingDirtyElements);assert(() {
      debugBuildingDirtyElements = true;
      return true; } ());try {
      if(renderViewElement ! =null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame(); // Call the drawFrame method of Renderbinding
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true; } ()); } profile(() {if (_needToReportFirstFrame && _reportFirstFrame) {
        developer.Timeline.instantSync('Widgets completed first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        _needToReportFirstFrame = false; }}); }Copy the code

Four,

  • widgetThe function of theelementProvide configuration information for eachwidgetInside Flutter is a configuration data, and represents the elements behind the screenelementWhile the real layout, rendering is throughRenderObjectThe main flow from creation to rendering is:widgetInformation generatedelement, and create the correspondingRenderObjectAssociated with theElement.renderObjectProperty, last passRenderObjectLayout and drawing.
  • The main process of Flutter from startup to display image on the screen is: first listen processingwindowObject to wrap these event handlers as Framework models for distribution throughwidgetcreateelementTree, and then throughscheduleWarmUpFrameRender and passRendererbindingLayout, draw, and finally callui.window.render(scene)The Scene information is sent to the Flutter engine, which calls the rendering API to draw the image on the screen.

Reference:

  • Flutter development documentation
  • Flutter quickly onto widgets
  • Flutter of actual combat

If there are mistakes, welcome to point out correction, thank you ~