Dart file main function to start a Flutter:

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

The main function calls runApp:

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

The cascade operator (..) is useful in the Dart syntax , represents the meaning of WidgetsFlutterBinding. The ensureInitialized () of the generated object call scheduleAttachRootWidget respectively and scheduleWarmUpFrame these two methods.

To recap, these three lines of code are important:

  1. A bridge object between the Flutter Engine(C++ code) and the Flutter Framework(Dart code) is officially defined as a glue object.
  2. Generate a render tree according to app;
  3. Draw the warm-up frame and render the Layer generated by the render tree onto the Flutter View via the Flutter Engine.

The summary is simple, but the content involved is quite complex. Let’s take a look at the flow from these three lines of code.

WidgetsFlutterBinding

All the code in the WidgetsFlutterBinding class is as follows:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, Static WidgetsBinding ensureInitialized() {if (WidgetsBinding. Instance == null) // Constructor call WidgetsFlutterBinding(); WidgetsBinding return WidgetsBinding. Instance! ; }}Copy the code

WidgetsFlutterBinding is derived from BindingBase and mixed with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding and WidgetsBinding7 mixins. The functionality of these seven mixins will be explained later.

The ensureInitialized method is the process of getting a WidgetsBinding. Instance singleton. Since mixins have no constructors, WidgetsFlutterBinding() actually calls the constructor of the parent BindingBase class.

BindingBase() {// call initInstances initInstances(); }Copy the code

All seven mixins mixed in with WidgetsFlutterBinding overwrite the initInstances() method, so their initInstances() are called. The final call logic is shown below:

High cohesion, low coupling and single module responsibility are achieved through sophisticated mixin code design, and serial execution sequence of initInstances() method calls is implemented through mixin dependencies.

FlutterView

Question: Why do I have to introduce FlutterView objects?

FlutterView is a user interface and event interface that the Flutter Engine provides to the Flutter Framework. The Flutter Framework can be understood as a processing Framework around FlutterView. So its importance is self-evident.

The mixins in the above WidgetsFlutterBinding are mainly used to handle callback events and render submissions for window objects (i.e., FlutterView objects), so it is necessary to introduce FlutterView first.

The window object is a variable in BindingBase, and its name implies that it is a singleton:

<! -- BindingBase --> ui.SingletonFlutterWindow get window => ui.window;Copy the code

The UI. The window is PlatformDispatcher. Instance windowId 0 in the main window:

<! -- window.dart --> final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);Copy the code

The inheritance graph of SingletonFlutterWindow is as follows:

<! -- window.dart --> abstract class FlutterView {} class FlutterWindow extends FlutterView {} class SingletonFlutterWindow  extends FlutterWindow {}Copy the code
FlutterView
abstract class FlutterView {
  // 
  PlatformDispatcher get platformDispatcher;

  // 
  ViewConfiguration get viewConfiguration;

  // 
  double get devicePixelRatio => viewConfiguration.devicePixelRatio;

  //
  Rect get physicalGeometry => viewConfiguration.geometry;

  //
  Size get physicalSize => viewConfiguration.geometry.size;

  // 
  WindowPadding get viewInsets => viewConfiguration.viewInsets;

  // 
  WindowPadding get viewPadding => viewConfiguration.viewPadding;

  //
  WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets;

  //
  WindowPadding get padding => viewConfiguration.padding;

  // 
  void render(Scene scene) => _render(scene, this);
  void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render';
}

Copy the code

FlutterView has several important properties and methods:

  1. PlatformDispatcher is the core of FlutterView, which is a layer of encapsulation. FlutterView is a class that actually sends messages to the Flutter Engine and gets callbacks.
  2. ViewConfigurationisPlatform ViewDescription of some information, which mainly includes several information:
    • devicePixelRatio: Ratio of physical pixels to virtual pixels. This is related to mobile phones, for example, an iPhone phone might be 2 or 3, an Android phone might be a decimal, such as 3.5, etc.
    • geometry:FlutterrenderingViewinNative platformLocation and size in.
    • viewInsets: the content displayed on each side and the size of the margin that can display the content; For example: when there is no keyboardviewInsets.bottomZero. When you have a keyboard, the keyboard blocks some area, and you can’t display anything under the keyboard, soviewInsets.bottomIt becomes the height of the keyboard.
    • padding: The display area of the SYSTEM UI, such as the status bar, should not display content in this area, otherwise it may be overwritten. The bangs on the top of many iphones, for example,padding.topThat’s its height.
    • viewPadding:viewInsetsandpaddingAnd.Refer to the address
  3. The following properties are exposed to the internal properties of the ViewConfiguration for external access.
  4. renderBy rendering the content generated by the Flutter code (Layer TreeThe generatedScene) is passed to theFlutter Engine, let the GPU render.

ViewConfiguration is actually obtained from PlatformDispatcher as well.

FlutterWindow

FlutterWindow does nothing but encapsulate a constructor. We won’t analyze it. Let’s take a look at some important code for SingletonFlutterWindow:

  • devicePixelRatio.physicalSize.paddingandviewInsetsSuch changes will trigger the callbackonMetricsChanged;

This is essentially a callback forward to platformDispatcher, and the following callback methods are similar.

VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
set onMetricsChanged(VoidCallback? callback) {
    platformDispatcher.onMetricsChanged = callback;
}
Copy the code
  • Where the phone is set (such as mainland China), and the callback received after the set locale is changedonLocaleChanged;
Locale get locale => platformDispatcher.locale;

VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
set onLocaleChanged(VoidCallback? callback) {
    platformDispatcher.onLocaleChanged = callback;
}  
Copy the code
  • Callback after text zoom changesonTextScaleFactorChanged;
  VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
  set onTextScaleFactorChanged(VoidCallback? callback) {
    platformDispatcher.onTextScaleFactorChanged = callback;
  }
Copy the code
  • platformBrightnessA callback after the changeonPlatformBrightnessChanged;
VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged;
  set onPlatformBrightnessChanged(VoidCallback? callback) {
    platformDispatcher.onPlatformBrightnessChanged = callback;
}
Copy the code
  • Flutter EngineAccording to theVSyncSend the callback ready to start the next frameonBeginFrame;
FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
Copy the code
  • onBeginFrameWhen done, start drawing the frame’s callbackonDrawFrame;
  VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
  set onDrawFrame(VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
  }
Copy the code
  • Callbacks to user gestures (click, swipe, etc.)onPointerDataPacket;
PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
    platformDispatcher.onPointerDataPacket = callback;
  }
Copy the code
  • A callback to the message sent by the plug-in was receivedonPlatformMessage;
PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage;
  set onPlatformMessage(PlatformMessageCallback? callback) {
    platformDispatcher.onPlatformMessage = callback;
  }
Copy the code
  • Semantics set and modified callback;
void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update);

VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged;
  set onAccessibilityFeaturesChanged(VoidCallback? callback) {
    platformDispatcher.onAccessibilityFeaturesChanged = callback;
  }
Copy the code
Conclusion:

The FlutterView object Window is essentially a wrapper around PlatformDispatcher. It obtains interface information from PlatformDispatcher, gets events sent from the Flutter Engine, and then triggers and forwards the corresponding callback methods.

If you have an idea, you can implement your own Flutter Framework based on Windows.

BindingBase

The main functions of BindingBase have been explained previously, so here’s a summary:

  • Constructor callinitInstancesMethod, in order to call 7 of them in sequencemixintheinitInstancesMethods.
  • Provided awindowThe singleton.
Abstract Class BindingBase {BindingBase() {// Initialize initInstances(); } / / singleton Windows UI. SingletonFlutterWindow get window = > UI. The window; }Copy the code

RendererBinding

The functionality of RendererBinding is primarily related to rendering trees. Let’s take a look at its important code:

  • initInstancesInitialization method:
void initInstances() { super.initInstances(); _instance = this; // 1 _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); // 2 window .. onMetricsChanged = handleMetricsChanged .. onTextScaleFactorChanged = handleTextScaleFactorChanged .. onPlatformBrightnessChanged = handlePlatformBrightnessChanged .. onSemanticsEnabledChanged = _handleSemanticsEnabledChanged .. onSemanticsAction = _handleSemanticsAction; // 3 initRenderView(); _handleSemanticsEnabledChanged(); // 4 addPersistentFrameCallback(_handlePersistentFrameCallback); // 5 initMouseTracker(); }Copy the code
  1. A PipelineOwner object is generated. Its main function is to collect the RenderObjects that need to be updated and then refresh the UI with the RendererBinding.
  2. To deal withwindowThe object’sonMetricsChanged.onTextScaleFactorChangedEtc callback method.
  3. initRenderViewGenerated aRenderViewobjectrenderViewAnd thenrenderViewSet to_pipelineOwnerThe root noderootNode.

The renderView is the root node of the render tree, and our MyApp will be inserted as its child node. I’ll tell you the plot. I’ll tell you later.

  1. addPersistentFrameCallbackCall isSchedulerBindingThe method ofPersistentFrameCallbackThe main implementation isWidgetthebuild / layout / paintAnd so on.
<! -- SchedulerBinding.dart --> void addPersistentFrameCallback(FrameCallback callback) { _persistentCallbacks.add(callback); }Copy the code
  1. To generate aMouseTrackerObject, processinghitTestResultorPointerAddedEventandPointerRemovedEventEvents.
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) { if (hitTestResult ! = null || event is PointerAddedEvent || event is PointerRemovedEvent) { assert(event.position ! = null); _mouseTracker! .updateWithEvent(event, () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position)); } super.dispatchEvent(event, hitTestResult); }Copy the code

This is an important method of event passing, and we’ll see it again when we cover GestureBinding event passing.

  • drawFrameDrawing method
void drawFrame() {
    // 1
    pipelineOwner.flushLayout();
    // 2
    pipelineOwner.flushCompositingBits();
    // 3
    pipelineOwner.flushPaint();
    // 4
    if (sendFramesToEngine) {
      // 5
      renderView.compositeFrame(); // this sends the bits to the GPU
      // 6
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    }
  }
Copy the code
  1. pipelineOwner.flushLayoutIs theDirty RenderObjectLayout positioning;
  2. pipelineOwner.flushCompositingBitsIs the updateRenderObjecttheneedsCompositingProperty, which is used in many situations, such as Clip, Transform, etc.
  3. pipelineOwner.flushPaintIs in thePaintingContextrightRenderObjectDraw.
  4. renderView.compositeFrameMethod is to useSceneBuilderConvert the drawing result of the previous steps to oneScene(n.)Object, and then callwindowtherenderMethod is submitted to GUP for display as follows:
void compositeFrame() { ... final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer! .buildScene(builder); _window.render(scene); . }Copy the code

5. PipelineOwner. FlushSemantics update semantic auxiliary information.

SemanticsBinding

Semantics translates to Semantics and basically describes the UI information in your application. It is mainly used for screen reading on iOS and Android to help people with visual impairment. It can be convenient to search in web development.

Semantics is common in the Flutter Framework, but is rarely used in mobile development. Let’s go over the initialization method briefly:

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

PaintingBinding

Don’t be misled by its name, it’s actually a mixin that handles image caching. It has nothing to do with RenderObject Paint.

Now let’s look at the main code for PaintingBinding:

  • initInstancesInitialization method
mixin PaintingBinding on BindingBase, ServicesBinding { @override void initInstances() { super.initInstances(); _instance = this; _imageCache = createImageCache(); shaderWarmUp? .execute(); }Copy the code
  1. _imageCacheIs the picture cache class, the maximum can store 1000 pictures, the maximum memory is 100MB;
  2. shaderWarmUp? .execute()An asynchronous method that initializes a default shader to avoid frame drops when the shader is needed.

Reduce shader compilation jank on mobile

  • handleMemoryPressureHandling memory warnings
void handleMemoryPressure() { super.handleMemoryPressure(); imageCache? .clear(); }Copy the code

Image storage is very memory intensive, so you need to clear the cache when the App memory warns.

ServicesBinding

The main function of a ServicesBinding is to receive messages from methodchannels and SystemChannels. Let’s look at the main code for ServicesBinding:

  • initInstancesInitialization method
void initInstances() {
    super.initInstances();
    _instance = this;
    // 1
    _defaultBinaryMessenger = createBinaryMessenger();
    // 2
    _restorationManager = createRestorationManager();
    // 3
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    // 4
    initLicenses();
    // 5
    SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    // 6
    readInitialLifecycleStateFromNativeWindow();
}
Copy the code
  1. createBinaryMessenger()Created aMethodChannel;
  2. createRestorationManager()Created aRestorationManagerFunction for recovering interface data;

This scenario mainly refers to the fact that the mobile App may have been killed after entering the background (releasing resources for other apps to run in the foreground process). By restoring data, users can feel that the mobile App has been running in the background when the App is switched back.

  1. Created in step 1_defaultBinaryMessengerThe implementation andThe Plugin plug-inThe communication
  2. initLicensesIt’s for some filesLicensesInstructions;
  3. Receive memory warnings and incoming lifecycle callbacks from SystemChannels;
Future<void> handleSystemMessage(Object systemMessage) async {
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'memoryPressure':
        handleMemoryPressure();
        break;
    }
    return;
  }
Copy the code
  1. The current lifecycle state is read and processed in the parent SchedulerBinding mixin.

SchedulerBinding

SchedulerBinding handles task scheduling. There are several scheduling stages in Flutter:

  1. idle

There is no frame drawing Task processing in this phase, but tasks, microTasks, Timer callbacks, user input and gestures, and a few other tasks.

  1. transientCallbacks

This stage mainly deals with the calculation and update of animation state

  1. midFrameMicrotasks

This phase handles Microtasks triggered by the transientCallbacks phase

  1. persistentCallbacks

This phase deals with build/ Layout /paint, as described in the RendererBinding section

  1. postFrameCallbacks

This stage is mainly about cleaning up or preparing for the next frame

Next, let’s look at the important code for SchedulerBinding:

  • handleAppLifecycleStateChanged
AppLifecycleState? get lifecycleState => _lifecycleState;
void handleAppLifecycleStateChanged(AppLifecycleState state) {
    assert(state != null);
    _lifecycleState = state;
    switch (state) {
      case AppLifecycleState.resumed:
      case AppLifecycleState.inactive:
        _setFramesEnabledState(true);
        break;
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
        _setFramesEnabledState(false);
        break;
    }
}
Copy the code
void _setFramesEnabledState(bool enabled) {
    if (_framesEnabled == enabled)
      return;
    _framesEnabled = enabled;
    if (enabled)
      scheduleFrame();
}
Copy the code

Listen for life cycle changes, life cycle status changes set the value of _framesEnabled, if _framesEnabled is false stop refresh interface; If _framesEnabled is true, scheduleFrame is called to request the Native Platform to refresh the view.

  • scheduleFrame
void scheduleFrame() { if (_hasScheduledFrame || ! framesEnabled) return; // 1 ensureFrameCallbacksRegistered(); // 2 window.scheduleFrame(); _hasScheduledFrame = true; }Copy the code
  1. ensureFrameCallbacksRegistered()Is to make sure towindowregisteredonBeginFrameandonDrawFrameTwo important callback functions;
void ensureFrameCallbacksRegistered() { window.onBeginFrame ?? = _handleBeginFrame; window.onDrawFrame ?? = _handleDrawFrame; }Copy the code
  1. window.scheduleFrame()Is toNative platformMake a request to refresh the view; After sending this request,Native platformWill be called at the appropriate timeonBegineFrameandonDrawFrameThese two functions, these two callbacks, do the things you need to do to refresh the view, such as updating widgets, animations, and completing the rendering. Call it when you’re donewindow.scheduleFrame()And the loop continues until the program exits the foreground or the program exits.
  • handleBeginFrame
void handleBeginFrame(Duration? rawTimeStamp) { _hasScheduledFrame = false; try { _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
Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
Copy the code

The function of handleBeginFrame is to execute all functions in _transientCallbacks. Adding callbacks to transientCallbacks is primarily the Ticker. ScheduleTick method, which is part of the animation framework.

<! -- ticker.dart --> void scheduleTick({ bool rescheduling = false }) { _animationId = SchedulerBinding.instance! .scheduleFrameCallback(_tick, rescheduling: rescheduling); } <! -- schedulerBinding.dart --> int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) { scheduleFrame(); _nextFrameCallbackId += 1; _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling); return _nextFrameCallbackId; }Copy the code
  • handleDrawFrame
void handleDrawFrame() { try { // 1 _schedulerPhase = SchedulerPhase.persistentCallbacks; for (final FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!) ; // 2 _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (final FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!) ; } finally { _schedulerPhase = SchedulerPhase.idle; _currentFrameTimeStamp = null; }}Copy the code
final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];  
Copy the code

Two callbacks are executed in handleDrawFrame, persistentCallbacks and all callbacks in postFrameCallbacks.

  • Tasks-related code
SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy; static int _taskSorter (_TaskEntry<dynamic> e1, _TaskEntry<dynamic> e2) { return -e1.priority.compareTo(e2.priority); } final PriorityQueue<_TaskEntry<dynamic>> _taskQueue = HeapPriorityQueue<_TaskEntry<dynamic>>(_taskSorter); Future<T> scheduleTask<T>( TaskCallback<T> task, Priority priority, { String? debugLabel, Flow? flow, }) { final bool isFirstTask = _taskQueue.isEmpty; final _TaskEntry<T> entry = _TaskEntry<T>( task, priority.value, debugLabel, flow, ); _taskQueue.add(entry); if (isFirstTask && ! locked) _ensureEventLoopCallback(); return entry.completer.future; } void unlocked() { super.unlocked(); if (_taskQueue.isNotEmpty) _ensureEventLoopCallback(); } void _ensureEventLoopCallback() { assert(! locked); assert(_taskQueue.isNotEmpty); if (_hasRequestedAnEventLoopCallback) return; _hasRequestedAnEventLoopCallback = true; Timer.run(_runTasks); } void _runTasks() { _hasRequestedAnEventLoopCallback = false; if (handleEventLoopCallback()) _ensureEventLoopCallback(); } bool handleEventLoopCallback() { if (_taskQueue.isEmpty || locked) return false; final _TaskEntry<dynamic> entry = _taskQueue.first; if (schedulingStrategy(priority: entry.priority, scheduler: this)) { try { _taskQueue.removeFirst(); entry.run(); } catch (exception, exceptionStack) { } return _taskQueue.isNotEmpty; } return false; }Copy the code

A task is a custom set of tasks. There are several ways in which task is related, and the logic is clear:

  1. All tasks are placed in the HeapPriorityQueue, which has a lower priority than the animation, ensuring that the task will not be executed if there is an animation, ensuring the animation flow.
  2. In the non-rendering phase, tasks are executed one by one in order of priority until they are all executed.

If you need to execute quickly, you can use Future and Isolate.

ScheduleWarmUpFrame in the runapp function is the SchedulerBinding method that is called, as described separately below.

GestureBinding

GestureBinding deals with user operations:

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

The GestureBinding handles the window’s onPointerDataPacket method with _handlePointerDataPacket, which is the entry to the event.

  • _handlePointerDataPacketEvent handling method flow of
void _handlePointerDataPacket(ui.PointerDataPacket packet) { _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio)); if (! locked) _flushPointerEventQueue(); } void _flushPointerEventQueue() { while (_pendingPointerEvents.isNotEmpty) handlePointerEvent(_pendingPointerEvents.removeFirst()); } void handlePointerEvent(PointerEvent event) { _handlePointerEventImmediately(event); } void _handlePointerEventImmediately(PointerEvent event) { HitTestResult? hitTestResult; if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) { // 1 hitTestResult = HitTestResult(); // 2 hitTest(hitTestResult, event.position); // 3 if (event is PointerDownEvent) { _hitTests[event.pointer] = hitTestResult; } } else if (event is PointerUpEvent || event is PointerCancelEvent) { // 4 hitTestResult = _hitTests.remove(event.pointer); } else if (event.down) { hitTestResult = _hitTests[event.pointer]; } if (hitTestResult ! = null || event is PointerAddedEvent || event is PointerRemovedEvent) { // 5 dispatchEvent(event, hitTestResult); }}Copy the code

_handlePointerDataPacket through a series of method calls, call _handlePointerEventImmediately method at last.

  1. When the event is a PointerDownEvent or PointerHoverEvent, create a new HitTestResult object with a path property that records the node through which the event was passed.
  2. HitTestResult adds the GestureBinding to the path as well.
void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
}
Copy the code
  1. ifeventisPointerDownEventWill thiseventTo join the_hitTestsIn, in order toevent.down– It can be accessed even while moving.
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
Copy the code
  1. wheneventisPointerUpEventorPointerCancelEventWhen, put thiseventfrom_hitTestsRemoved.
  2. The last calldispatchEvent(event, hitTestResult)Methods.
  • dispatchEventmethods

If you remember, we mentioned the dispatchEvent method in RendererBinding.

<! -- rendererBinding.dart --> void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) { _mouseTracker! .updateWithEvent(event, () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position)); super.dispatchEvent(event, hitTestResult); }Copy the code

Some important call logic renderView. HitTestMouseTrackers (event. The position)), will from renderView traverse its child, the Widget to join along the path.

The code is as follows:

<! -- view.dart --> HitTestResult hitTestMouseTrackers(Offset position) { final BoxHitTestResult result = BoxHitTestResult(); hitTest(result, position: position); return result; } bool hitTest(HitTestResult result, { required Offset position }) { if (child ! = null) child! .hitTest(BoxHitTestResult.wrap(result), position: position); result.add(HitTestEntry(this)); return true; } <! -- box.dart --> bool hitTest(BoxHitTestResult result, { required Offset position }) { if (_size! .contains(position)) { if (hitTestChildren(result, position: position) || hitTestSelf(position)) { result.add(BoxHitTestEntry(this, position)); return true; } } return false; }Copy the code

When all the widgets in the renderView are iterated, the hitTestResult is returned to the dispatchEvent method of the GestureBinding, and the handleEvent method is then iterated through the path array one by one.

<!-- gestureBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    for (final HitTestEntry entry in hitTestResult.path) {
      entry.target.handleEvent(event.transformed(entry.transform), entry);
    }
}

void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
}
Copy the code

The handleEvent method ends up doing some routing and gesture handling.

The description of the event processing link is complete.

WidgetsBinding

WidgetsBinding deals with the logic of the Widget Tree:

  • initInstancesInitialization method
void initInstances() { super.initInstances(); _instance = this; // 1 _buildOwner = BuildOwner(); buildOwner! .onBuildScheduled = _handleBuildScheduled; // 2 window.onLocaleChanged = handleLocaleChanged; window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; }Copy the code
  1. Initialize a BuildOwner object that performs the Widget Tree build task;
  2. Some window callbacks are performed.

At this point, the first step WidgetsFlutterBinding. The ensureInitialized () which was involved in the detailed introduction. Now let’s move on to the second stage.

scheduleAttachRootWidget

EnsureInitialized’s presentation expanded a lot to give you an overview of the framework. ScheduleAttachRootWidget is a step by step code flow.

void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } void attachRootWidget(Widget rootWidget) { _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner! , renderViewElement as RenderObjectToWidgetElement<RenderBox>?) ; }Copy the code
  • scheduleAttachRootWidgetAsynchronously calledattachRootWidgetMethods.
  1. attachRootWidgetAn initializer is initialized inRenderObjectToWidgetAdapterObject, the constructor is passed inrenderViewandrootWidget.renderViewisRendererBindingtheinitInstancesThe object that is initialized in the method,rootWidgetIt’s the interface that we writeMyApp().

As we can see from the constructor argument name, renderView is the container and rootWidget is the child of the container. That is, the renderView is the root of all widgets.

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
Copy the code

Fun: RenderObjectToWidgetAdapter is actually a RenderObjectWidget subclasses, add a Adapter is a bit misleading.

  1. RenderObjectToWidgetAdapterThe objectattachToRenderTreeMethod, pass in the constructor tool **_buildOwner**.
  • attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) { if (element == null) { owner.lockState(() { // 1 element = createElement(); element! .assignOwner(owner); }); owner.buildScope(element! , () { // 2 element! .mount(null, null); }); // 3 SchedulerBinding.instance! .ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element! ; }Copy the code
  1. Creates a RenderObjectElement RenderObjectToWidgetElement subclass, and the construction tools buildOwner references to it;
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
Copy the code
  1. elementcallmountMethods.
  2. Tell Native Platform in advance that it wants to refresh the interface.
  • RenderObjectToWidgetElement mount
// RenderObjectToWidgetElement void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); _rebuild(); } // RenderObjectElement void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); attachRenderObject(newSlot); _dirty = false; } // Element void mount(Element? parent, dynamic newSlot) { _parent = parent; _slot = newSlot; _lifecycleState = _ElementLifecycle.active; _depth = _parent ! = null ? _parent! .depth + 1 : 1; if (parent ! = null) _owner = parent.owner; final Key? key = widget.key; if (key is GlobalKey) { key._register(this); } _updateInheritance(); }Copy the code
  1. RenderObjectToWidgetElementthemountMethod first callsElementthemountMethods. The main function is to set_parent._slot._owner._depthThe value of the;

_slot is the location of the child Element on the parent node. _depth is the depth of the child Element on the Element tree.

  1. And then callRenderObjectElementthemountMethods. Created arenderObjectIn fact, it isrenderView. And then take thisrenderObjectMount to theRenderObject TreeGo, the previous oneRenderObject TreeThere’s no content, sorenderViewThat’s the root node;

Flutter has three trees: Widget Tree, Element Tree and RenderObject Tree. The RenderObject Tree is the actual rendered content.

  • RenderObjectToWidgetElement _rebuild
void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
    } catch (exception, stack) {
    }
}
Copy the code

The _rebuild function is the Build child Widget, in this case Build MyApp.

Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) { final Element newChild; if (child ! = null) { if (hasSameSuperclass && child.widget == newWidget) { if (child.slot ! = newSlot) updateSlotForChild(child, newSlot); newChild = child; } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { if (child.slot ! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); newChild = child; } else { deactivateChild(child); newChild = inflateWidget(newWidget, newSlot); }} else {// Create Element newChild = inflateWidget(newWidget, newSlot); } return newChild; }Copy the code

If child is null and newWidget is not null in updateChild, newChild = inflateWidget(newWidget, newSlot) is called. .

Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }
Copy the code

The inflateWidget first creates an Element, which then calls the mount method.

With another way mount, you guessed it, using buildOwner renderview – to the Widget tree > MyApp – > MaterialApp… Keep building until the traversal is complete.

scheduleWarmUpFrame

ScheduleWarmUpFrame is a SchedulerBinding method:

void scheduleWarmUpFrame() {
    Timer.run(() {
      handleBeginFrame(null);
    });
    Timer.run(() {
      handleDrawFrame();
      if (hadScheduledFrame)
        scheduleFrame();
    });

    lockEvents(() async {
      await endOfFrame;
    });
}
Copy the code

ScheduleWarmUpFrame is to call the handleBeginFrame and handleDrawFrame methods to draw a frame and present it to the GPU for display.

It is important to note that scheduleWarmUpFrame is drawn immediately, without waiting for Vsyn notification, because it should start as soon as possible.

LockEvents are also used to wait for the reservation frame to be drawn before performing other tasks.

What is it drawn? The Layer Tree corresponding to the RenderObject Tree is drawn and presented to GPU for display in the form of Scene.