preface

Today we are going to analyze the startup process of Flutter and its rendering process, and make a simple analysis of it.

Start the process

Dart the entry to Flutter startup is found in the main() function in lib/main.dart, which is the starting point for dart applications. The simplest implementation of the main function is as follows:

void main() => runApp(MyApp());
Copy the code

As you can see, the main function only calls the runApp() method. Let’s see what’s going on inside:

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

WidgetsFlutterBinding is the bridge that binds the Widget to the Flutter engine. It is defined as follows:

/// Specific bindings for an application based on the Widgets framework.
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

As you can see, WidgetsFlutterBinding is inherited from BindingBase and mixed with many bindings. Before introducing these bindings, we will introduce Windows.

The most basic interface to the host operating system’s user interface.

The most basic user interface of the host OS.

Clearly, Window is the interface that the Flutter Framework uses to connect to the host operating system.

Let’s look at some of the Window class definitions:

@Native("Window,DOMWindow")
class Window extends EventTarget  implements WindowEventHandlers.WindowBase  GlobalEventHandlers._WindowTimers.WindowBase64 {
          
  // The DPI of the current device, that is, how many physical pixels are displayed in a logical pixel. The larger the number, the finer the fidelity of the display.
  // DPI is the firmware attribute of the device screen. For example, on the Nexus 6 screen, DPI is 3.5
  double get devicePixelRatio => _devicePixelRatio;
  
  // The size of the Flutter UI drawing region
  Size get physicalSize => _physicalSize;

  // The default Locale of the current system
  Locale get locale;
    
  // Current system font scale.
  double get textScaleFactor => _textScaleFactor;  
    
  // Callback when drawing area size changes
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Callback for Locale changes
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // System font scaling changes callback
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  The draw-before-callback, typically driven by the monitor's VSync signal, is called when the screen refreshes
  FrameCallback get onBeginFrame => _onBeginFrame;
  // Draw callback
  VoidCallback get onDrawFrame => _onDrawFrame;
  // Click or pointer event callback
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  OnBeginFrame and onDrawFrame will then be called when appropriate,
  This method calls the Window_scheduleFrame method of the Flutter engine directly
  void scheduleFrame() native 'Window_scheduleFrame';
  This method calls the Window_render method of the Flutter Engine directly
  void render(Scene scene) native 'Window_render';

  // Send platform messages
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // Platform channel message processing callback
  PlatformMessageCallback getonPlatformMessage => _onPlatformMessage; .// Other attributes and callbacks
    
}        
Copy the code

You can see that the Window contains information about the current device and system and some callbacks to the Flutter Engine.

Now let’s go back and look at the various bindings that WidgetsFlutterBinding mixes into. Looking at the source code for these bindings, we can see that these bindings basically listen for and handle events in the Window object, then wrap these events into the Framework’s model, abstract them, and distribute them. You can see that the WidgetsFlutterBinding is the glue that binds the Flutter Engine to the upper Framework.

  • GestureBinding: provides a window onPointerDataPacket callback, binding Fragment gestures subsystem, is the binding Framework model and the underlying events entrance.

    mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
      @override
      void initInstances() {
        super.initInstances();
        _instance = this;
        window.onPointerDataPacket = _handlePointerDataPacket; }}Copy the code
  • ServiceBinidng: Provides the window.onPlatformMessage callback. Users bind platform message channels to handle native and Flutter communications.

    mixin SchedulerBinding on BindingBase {
      @override
      void initInstances() {
        super.initInstances();
        _instance = this;
        if(! kReleaseMode) { addTimingsCallback((List<FrameTiming> timings) { timings.forEach(_profileFramePostEvent); }); }}Copy the code
  • SchedulerBinding: Provides window.onBeginFrame and Window. onDrawFrame callbacks, listens for refresh events, and binds the Framework to draw scheduling subsystems.

  • PaintingBinding: Binds the drawing library to the main user to handle the image cache

  • SemanticsBidning: A bridge between the semantic layer and the Flutter engine, mainly providing low-level support for ancillary functions.

  • RendererBinding: provides a window onMetricsChanged, window onTextScaleFactorChanged callback, etc. It is the bridge between the render tree and the Flutter engine.

  • WidgetsBinding: Provides callbacks to window.onLocaleChange, onBulidScheduled, etc. It is the bridge between the Flutter Widget layer and engine.

WidgetsFlutterBinding. EnsureInitiallized () is responsible for the initialization of a global widgetsBinding singleton, then invokes the WidgetBinding attachRootwWidget method, This method is responsible for adding the root Widget to the RenderView as follows:

void scheduleAttachRootWidget(Widget rootWidget) {
  Timer.run(() {
    attachRootWidget(rootWidget);
  });
}
Copy the code
void attachRootWidget(Widget rootWidget) {
  final bool isBootstrapFrame = renderViewElement == null;
  _readyToProduceFrames = true;
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner! , renderViewElementasRenderObjectToWidgetElement<RenderBox>?) ;if (isBootstrapFrame) {
    SchedulerBinding.instance!.ensureVisualUpdate();
  }
}
Copy the code

Notice that renderView and renderViewElement are two variables in the code. RenderView is a Renderobject that is the root of the rendering tree. RenderViewElement is the Element object corresponding to the renderView.

AttachToRenderTree (attachToRenderTree) attachToRenderTree (attachToRenderTree) attachToRenderTree (attachToRenderTree)

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

This method is responsible for creating the root element, namely RenderObjectToWidgetElement, element and the associated with the widget, which create the widget tree corresponding element tree.

If element is already created, the widget associated with the root Element is set to new so that you can see that element is only created once and will be reused later. So what is BuildOwner? , which is essentially the widget framework’s management class that keeps track of which widgets need to be rebuilt.

After the component tree is built, it goes back to the runApp implementation. When attachRootWidget is deployed, the last line calls the scheduleWarmUpFrame() method of the WidgetsFlutterBainding instance. This method is currently in a SchedulerBinding. It will draw immediately after it is called. Before the draw is complete, the method locks event distribution.

conclusion

From the above analysis we can see that WidgetsFlutterBinding is like a glue that listens for and handles the events of the Window object, wraps and distributes these events according to the framework model. So widgetsFlutterBinding is the glue that connects the Flutter Engine to the upload Framework.

WidgetsFlutterBinding.ensureInitialized() .. scheduleAttachRootWidget(app) .. scheduleWarmUpFrame();Copy the code
  • EnsureInitialized: Performs initializationWidgetsFlutterBindingAnd listens for the window’s events for wrapping distribution.
  • ScheduleAttachRootWidget: In a subsequent step of this method, the root is createdElement, the callmountcompleteelementRenderObjectTo create the tree
  • ScheduleWarmUpFrame: Starts drawing the first frame

Render the official line

Frame

A drawing process can be called a frame. We know that the Flutter can achieve 60 FPS, which means 60 redraws in 1 second. The larger the FPS, the smoother the interface will be.

It is important to note that the frame in a Flutter is not equal to the screen refresh frame, because the Flutter UI frame is not triggered every time the screen refresh occurs. This is because if the UI does not change for a period of time, it is not necessary to go through the rendering process again every time. Therefore, Flutter will actively request frames after the first frame has been rendered and will only go through the rendering process again if the UI might change.

1, the Flutter registers an onBeginFrame and an onDrawFrame callback on the window. The onDrawFrame callback will eventually call the drawFrame.

2. When we call the window.scheduleFrame method, the Flutter engine will call onBeginFrame and onDrawFrame at the appropriate time (i.e. before the next screen refresh, depending on the Flutter engine implementation).

OnBeginFrame and onDrawFrame are registered before window.scheduleFrame is called, as shown below:

void scheduleFrame() {
  if(_hasScheduledFrame || ! framesEnabled)return;
  assert(() {
    if (debugPrintScheduleFrameStacks)
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase. ');
    return true; } ()); ensureFrameCallbacksRegistered();window.scheduleFrame();
  _hasScheduledFrame = true;
}

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

As you can see, drawFrame is called only after scheduleFrame is actively called (this method is a registered callback).

So when a Flutter refers to frame, unless otherwise specified, it corresponds to a drawFrame(), not to a screen refresh.

Frame Processing Flow

When there is a new frame comes, began to call SchedulerBinding. HandleDrawFrame to handle frame, the specific process is to perform four task queue: TransientCallbacks, midFrameMicotasks, persistentCallbacks, postFrameCallbacks. The current frame ends when all four task queues are complete.

To sum up, Flutter divides its entire lifecycle into five states, which are represented by the SchedulerPhase:

enum SchedulerPhase {
  /// Idle state, where no frame is being processed. This state indicates that the page has not changed and does not need to be rerendered
  /// If the page changes, scheduleFrame needs to be called to request the frame.
  /// Note that the idle state simply means that no frame is being processed. Usually microtasks, timer callbacks, or user callback events can be executed
  /// Let's say we're listening for a TAP event, and when the user clicks on it, that's where the onTap callback is executed
  idle,

  /// Execute a temporary callback task. The temporary callback task can be executed only once and removed from the temporary task queue.
  /// Typically, animation callbacks are executed at this stage
  transientCallbacks,

  /// It is possible to create new microtasks when executing temporary tasks, such as Fluture when executing the first temporary task,
  /// And the Future will be resolved before all tasks are completed
  /// In this case the Future's callback will be executed in the [midFrameMicrotasks] phase
  midFrameMicrotasks,

  /// Perform persistent tasks (tasks to be performed by each frame) such as render official lines (build, layout, draw)
  /// Is executed in this task queue
  persistentCallbacks,

  /// PostFrameCallbacks are executed before the current frame ends, usually doing some cleanup and requesting a new frame
  postFrameCallbacks,
}
Copy the code

Note that the rendering pipeline that will be highlighted next is executed inside the persistentCallbacks.

Rendering pipline (RENDERING pipline)

When our page needs to change, we need to call scheduleFrame() to request a frame, which registers _handleBeginFrame and _handleDrawFrame. When frame arrives, _handleDrawFrame is executed as follows:

void _handleDrawFrame() {
  // Determine whether the current frame needs to be delayed because the current frame is preheated
  if (_rescheduleAfterWarmUpFrame) {
    _rescheduleAfterWarmUpFrame = false;
    // Add a callback that will be executed after the current frame ends
    addPostFrameCallback((Duration timeStamp) {
      _hasScheduledFrame = false;
      // request the frame again.
      scheduleFrame();
    });
    return;
  }
  handleDrawFrame();
}
Copy the code
void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // end the "Animate" phase
  try {
    // Toggle the current life cycle status
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
     // Perform a persistent task callback,
    for (final FrameCallback callback in_persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!) ;/ / postFrame callback
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    final List<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (final FrameCallback callback inlocalPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!) ; }finally {
     // Change the state to idle
    _schedulerPhase = SchedulerPhase.idle;
    Timeline.finishSync(); // end the Frame
     //....
    _currentFrameTimeStamp = null; }}Copy the code

In the code above, the persistent task is iterated over and the callback is performed, corresponding to _persistentCallbacks. Through analysis of the call stack, It turns out that this callback was added to _persistentCallbacks when RendererBinding was initialized:

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    // Add persistent task callback......
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      // Add postFrame task callbackaddPostFrameCallback(_handleWebFirstFrame); }}void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }
Copy the code

So eventually the callback is _handlePersistentFrameCallback

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}
Copy the code

In the above code, the drawFrame method is called.


DrawFrame () : WidgetsBinding drawFrame() : WidgetsBinding drawFrame() : WidgetsBinding drawFrame() :

void drawFrame() {
  .....// ellipsis is irrelevant
  try {
    if(renderViewElement ! =null) buildOwner! .buildScope(renderViewElement!) ;Rebuild the widget tree
    super.drawFrame();
    buildOwner!.finalizeTree();
  } 
}
Copy the code

The final call looks like this:

void drawFrame() {
  assert(renderView ! =null);
  pipelineOwner.flushLayout(); // update the layout
  pipelineOwner.flushCompositingBits();//3. Update the layer composition information
  pipelineOwner.flushPaint(); / / 4. Redrawn
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // 5. On the screen, the bit data drawn will be sent to the GPU./////}}Copy the code

The code does five things:

1, Rebuild the Widget tree (buildScope())

FlushLayout ()

Update “layer compositing” information (flushCompositingBits())

4, flushPaint()

5, screen: display the drawn product on the screen

The above five steps are called rendering pipline, which translates to “rendering pipeline” and are the focus of attention. Let’s take the update process of setState as an example to get a deep impression of the whole update process.

SetState execution flow

void setState(VoidCallback fn) {
  assert(fn ! =null);
  // Execute callback. The return value cannot be future
  final Object? result = fn() as dynamic;
  assert(() {
    if (result is Future) {
      throw.//}} ()); _element! .markNeedsBuild(); }Copy the code
void markNeedsBuild() {
  ....//
  // annotate that the element needs to be rebuilt
  _dirty = true; owner! .scheduleBuildFor(this);
}
Copy the code
void scheduleBuildFor(Element element) {
 / / comment 1
 if(! _scheduledFlushDirtyElements && onBuildScheduled ! =null) {
     _scheduledFlushDirtyElements = true; onBuildScheduled! (a); }/ / comment 2
  _dirtyElements.add(element);
  element._inDirtyList = true;
}
Copy the code

When setState is called:

1. First call the markNeedsBuild method and mark Element’s dirty as true to indicate that it needs to be rebuilt

2, then call scheduleBuildFor to add the current element to the _dirtyElements list (note 2)

Let’s focus on the code for comment 1,

First determines _scheduledFlushDirtyElements if false, the field values default is false, the initial value and then determine onBuildScheduled is not null, OnBuildScheduled is already created when WidgetBinding is initialized, so it will not be null.

When the condition is true, the onBuildScheduled callback is executed directly. Let’s keep track:

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances(); .///  buildOwner! .onBuildScheduled = _handleBuildScheduled }Copy the code
void _handleBuildScheduled() {
  ...///
  ensureVisualUpdate();
}
Copy the code

From the code above we can see that onBuildScheduled is indeed initialized in the WidgetsBinding initialization method. And his implementation calls the ensureVisualUpdate method, so let’s follow up:

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

In the code above, the schedulerPhase state is determined and scheduleFrame is called when idle or postFrameCallbacks are in the state.

The meaning of each of the above states has been described above and will not be repeated here. It is worth mentioning that every time the frame process completes, the state is changed back to idle in the finally code block. This also means that if you setState frequently, no new render will be initiated if the last render process is not completed.

Moving on to scheduleFrame:

void scheduleFrame() {
  // Determine if the process has started
  if(_hasScheduledFrame || ! framesEnabled)return;
  / / comment 1
  ensureFrameCallbacksRegistered();
  / / comment 2
  window.scheduleFrame();
  _hasScheduledFrame = true;
}
Copy the code

Note 1: Register onBeginFrame and onDrawFrame as fields of function type are described in the “rendering pipeline” above.

Note 2: The Flutter framework wants the Flutter Engine to make a request, and the Flutter Engine will call onBeginFrame and onDrawFrame when appropriate. This timing can be considered prior to the next screen refresh, depending on the Flutter engine implementation.

At this point, the core of setState is that a request is triggered, onBeginFrame is called back on the next screen refresh, and the onDrawFrame method is called when it’s done.


void handleBeginFrame(Duration? rawTimeStamp) {
  ...///
  assert(schedulerPhase == SchedulerPhase.idle);
  _hasScheduledFrame = false;
  try {
    Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
    // Change the life cycle to transientCallbacks, indicating that some temporary task callbacks are being executed
    _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

The above code mainly executes the _transientCallbacks callback method. After execution, change the lifecycle to midFrameMicrotasks.

The next step is to execute the handlerDrawFrame method. This method has been analyzed above, and we know that it will eventually go into the drawFrame method.

# WidgetsBindign.drawFrame()
void drawFrame() { 
.....// ellipsis is irrelevant
  try {
    if(renderViewElement ! =null) buildOwner! .buildScope(renderViewElement!) ;Rebuild the widget tree
    super.drawFrame(); buildOwner! .finalizeTree(); } } # RendererBinding.drawFrame()void drawFrame() {
  assert(renderView ! =null);
  pipelineOwner.flushLayout(); // update the layout
  pipelineOwner.flushCompositingBits();//3. Update the layer composition information
  pipelineOwner.flushPaint(); / / 4. Redrawn
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // 5. On the screen, the bit data drawn will be sent to the GPU./////}}Copy the code

The above is the general process of calling setState. The actual process is more complicated, for example, setState is not allowed to be called again in this process, and the scheduling of animation in the frame is involved, as well as how to update and redraw the layout. From the above analysis, we need to get a deep impression of the whole process.

The drawing process in the drawFrame above will be covered in the next article.


Recommended reading

  • This article learns about BuildContext
  • The principle and use of Key
  • Construction process analysis of Flutter three trees

The resources

  • Blog.csdn.net/shuimu15792…
  • Because Chinese website

If this article is helpful to you, we are honored, if there are mistakes and questions in the article, welcome to put forward