Flutter系列三:Flutter啟動流程分析

chonglingliu發表於2021-03-17

本文我們來分析下Flutter的啟動流程,首先我們從main.dart檔案的main函式開始:

void main() => runApp(MyApp());
複製程式碼

main函式則呼叫的是runApp函式:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

函式中有用到Dart語法中的級聯運算子(..),代表的含義是WidgetsFlutterBinding.ensureInitialized()生成的物件分別呼叫了scheduleAttachRootWidgetscheduleWarmUpFrame這兩個方法。

先概括一下這三行程式碼的重要作用:

  1. 生成一個Flutter Engine(C++程式碼)Flutter Framework(Dart程式碼)中間橋接物件,官方定義為膠水物件;
  2. 根據app生成一個渲染樹;
  3. 繪製熱身幀, 將渲染樹生成的Layer圖層通過Flutter Engine渲染到Flutter View上。

概括起來很簡單,但是其中包含的內容是相當複雜的。我們接下來就從這三行程式碼入手分析一下其中具體的流程。

WidgetsFlutterBinding

WidgetsFlutterBinding類中的所有程式碼如下:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  // 類初始化方法
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      // 構造方法呼叫
      WidgetsFlutterBinding();
    // 返回物件WidgetsBinding
    return WidgetsBinding.instance!;
  }
}
複製程式碼

WidgetsFlutterBinding繼承自BindingBase,混入了GestureBindingSchedulerBindingServicesBindingPaintingBindingSemanticsBindingRendererBindingWidgetsBinding7個mixin。這7個mixin的功能後面詳解介紹。

ensureInitialized方法就是獲取WidgetsBinding.instance單例的過程。由於mixin沒有構造方法,所以WidgetsFlutterBinding()實際呼叫的是父類BindingBase的構造方法。

BindingBase() {
  // 呼叫initInstances
  initInstances();
}
複製程式碼

WidgetsFlutterBinding混入的7個mixin都重寫了initInstances()方法,所以他們的initInstances()都會被呼叫。最後的呼叫邏輯如下圖所示:

initInstances

通過精妙的mixin程式碼設計,實現了高內聚低耦合和模組職責單一,並且通過mixin依賴,實現了initInstances()方法呼叫的序列按執行順序。

FlutterView

問題:為什麼突兀的來介紹FlutterView物件呢?

FlutterViewFlutter EngineFlutter Framework開放的使用者介面和事件的介面,可以把Flutter Framework理解為圍繞FlutterView的一個處理框架。所以其重要性不言而喻。

上面WidgetsFlutterBinding混入的多個mixin主要就是處理window物件(即FlutterView物件的)的回撥事件和提交渲染內容,所以我們先來介紹一下FlutterView是非常有必要的。

window物件是BindingBase的一個變數, 名字上推測他就是個單例物件:

<!-- BindingBase -->
ui.SingletonFlutterWindow get window => ui.window;
複製程式碼

ui.windowPlatformDispatcher.instancewindowId為0的主window:

<!-- window.dart -->
final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);
複製程式碼

SingletonFlutterWindow的繼承圖譜如下:

<!-- window.dart -->
abstract class FlutterView {}
class FlutterWindow extends FlutterView {}
class SingletonFlutterWindow extends FlutterWindow {}
複製程式碼
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';
}

複製程式碼

FlutterView有幾個重要的屬性和方法:

  1. PlatformDispatcherFlutterView的核心,FlutterView是對它的一層封裝,是真正向Flutter Engine傳送訊息和得到回撥的類;
  2. ViewConfigurationPlatform View的一些資訊的描述,其中主要包括幾個資訊:
    • devicePixelRatio:物理畫素和虛擬畫素的比值。這個和手機有關,譬如iPhone手機可能是2或者3,Android手機就有可能是個小數,譬如3.5等。
    • geometryFlutter渲染的ViewNative platform中的位置和大小。
    • viewInsets: 各個邊顯示的內容和能顯示內容的邊距大小;譬如:沒有鍵盤的時候viewInsets.bottom為0,當有鍵盤的時候鍵盤擋住了一些區域,鍵盤底下無法顯示內容,所以viewInsets.bottom就變成了鍵盤的高度。
    • padding: 系統UI的顯示區域如狀態列,這部分割槽域最好不要顯示內容,否則有可能被覆蓋了。譬如,很多iPhone頂部的劉海區域,padding.top就是其高度。
    • viewPadding:viewInsetspadding的和。參考地址
  3. 下面的屬性都是對ViewConfiguration內部屬性的暴露,便於外部獲取。
  4. render方法是將Flutter程式碼生成的渲染內容(Layer Tree生成的Scene)傳遞給Flutter Engine, 讓GPU去渲染。

ViewConfiguration其實也是從PlatformDispatcher獲取的。

FlutterWindow

FlutterWindow沒有什麼功能,只是封裝了一個構造方法,我們不做分析,接下來我們來看看SingletonFlutterWindow的一些重要程式碼:

  • devicePixelRatio, physicalSize, paddingviewInsets等的變化會觸發的回撥onMetricsChanged

本質是轉發了platformDispatcher的回撥,後面的回撥方法都類似。

VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
set onMetricsChanged(VoidCallback? callback) {
    platformDispatcher.onMetricsChanged = callback;
}
複製程式碼
  • 手機設定的地區(如中國大陸),以及設定的地區更改後收到的回撥onLocaleChanged
Locale get locale => platformDispatcher.locale;

VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
set onLocaleChanged(VoidCallback? callback) {
    platformDispatcher.onLocaleChanged = callback;
}  
複製程式碼
  • 文字縮放倍率變化後的回撥onTextScaleFactorChanged;
  VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
  set onTextScaleFactorChanged(VoidCallback? callback) {
    platformDispatcher.onTextScaleFactorChanged = callback;
  }
複製程式碼
  • platformBrightness變化後的回撥onPlatformBrightnessChanged;
VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged;
  set onPlatformBrightnessChanged(VoidCallback? callback) {
    platformDispatcher.onPlatformBrightnessChanged = callback;
}
複製程式碼
  • Flutter Engine根據VSync傳送的準備開始下一幀的回撥onBeginFrame;
FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
複製程式碼
  • onBeginFrame完成後,開始繪製幀的回撥onDrawFrame;
  VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
  set onDrawFrame(VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
  }
複製程式碼
  • 使用者的手勢操作(點選,滑動等)的回撥onPointerDataPacket;
PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
    platformDispatcher.onPointerDataPacket = callback;
  }
複製程式碼
  • 收到外掛傳送的訊息的回撥onPlatformMessage;
PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage;
  set onPlatformMessage(PlatformMessageCallback? callback) {
    platformDispatcher.onPlatformMessage = callback;
  }
複製程式碼
  • 語義的設定和修改後的回撥;
void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update);

VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged;
  set onAccessibilityFeaturesChanged(VoidCallback? callback) {
    platformDispatcher.onAccessibilityFeaturesChanged = callback;
  }
複製程式碼
總結:

FlutterView物件window本質上是對PlatformDispatcher的封裝,從PlatformDispatcher獲取一些介面相關資訊,獲取從Flutter Engine 傳送來的事件,然後觸發和轉發相應的回撥方法。

如果有想法,可以基於window實現自己的Flutter Framework

BindingBase

BindingBase的主要功能前面都已經說明,這裡總結一下:

  • 建構函式呼叫initInstances方法,其實是為了依次呼叫7個mixininitInstances方法。
  • 提供了一個window單例。
abstract class BindingBase {
    BindingBase() {
        // 初始化
        initInstances();
    }
    // 單例window
    ui.SingletonFlutterWindow get window => ui.window;
}
複製程式碼

RendererBinding

RendererBinding的功能主要和渲染樹相關。我們來看看它的重要程式碼:

  • initInstances初始化方法:
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();
}
複製程式碼
  1. 生成了一個PipelineOwner物件。它的主要作用是收集需要更新的RenderObjects,然後藉助RendererBinding進行UI重新整理。
  2. 處理window物件的onMetricsChanged,onTextScaleFactorChanged等回撥方法。
  3. initRenderView生成了一個RenderView物件renderView, 然後將renderView設定為_pipelineOwner的根節點rootNode

這個renderView是渲染樹的根節點,我們的MyApp將作為它的子節點插入渲染樹。先劇透一下,後面會介紹。

  1. addPersistentFrameCallback呼叫的是SchedulerBinding的方法, PersistentFrameCallback主要執行的是Widgetbuild / layout / paint等一系列操作。
<!-- SchedulerBinding.dart -->
void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
}
複製程式碼
  1. 生成一個MouseTracker物件,處理hitTestResult或者PointerAddedEventPointerRemovedEvent事件。
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);
  }
複製程式碼

這裡是事件傳遞的重要方法,後面介紹GestureBinding事件傳遞的時候會再次見到它。

  • drawFrame繪製方法
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.
    }
  }
複製程式碼
  1. pipelineOwner.flushLayout是對Dirty RenderObject進行佈局定位;
  2. pipelineOwner.flushCompositingBits是更新RenderObjectneedsCompositing屬性,這個屬性在很多情況下需要用到,譬如裁剪(Clip),旋轉(Transform)等。
  3. pipelineOwner.flushPaint是在PaintingContextRenderObject進行繪製。
  4. renderView.compositeFrame方法是用SceneBuilder將前幾步的繪製結果轉換成一個Scene(可以理解為一幀畫面)物件,然後呼叫windowrender方法提交給GUP去顯示,程式碼如下:
void compositeFrame() {
    ...
    final ui.SceneBuilder builder = ui.SceneBuilder();
    final ui.Scene scene = layer!.buildScene(builder);
    _window.render(scene);
    ...
  }
複製程式碼

5.pipelineOwner.flushSemantics更新語義輔助資訊。

SemanticsBinding

Semantics譯來就是語義,主要就是描述應用程式中的UI資訊。在iOSAndroid主要是用於讀屏使用,幫助有視力障礙的人使用。在網頁開發中可以方便搜尋等。

Flutter FrameworkSemantics非常常見,但是其實在移動端開發中,這個功能其實很少使用到。我們就一筆帶過,簡單看下它的初始化方法:

mixin SemanticsBinding on BindingBase {
    void initInstances() {
        super.initInstances();
        _instance = this;
        _accessibilityFeatures = window.accessibilityFeatures;
    }
}
複製程式碼

PaintingBinding

不要被它的名字誤導了,其實它是處理圖片快取的mixin。和RenderObjectPaint沒啥關係。

接下來我們看看PaintingBinding的主要程式碼:

  • initInstances初始化方法
mixin PaintingBinding on BindingBase, ServicesBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _imageCache = createImageCache();
    shaderWarmUp?.execute();
}
複製程式碼
  1. _imageCache是圖片快取的類,最大能存1000張圖片,最大記憶體是100MB;
  2. shaderWarmUp?.execute()是一個非同步方法,初始化了一個預設的著色器,避免需要著色器的時候再初始化出現掉幀現象。

Reduce shader compilation jank on mobile

  • handleMemoryPressure處理記憶體警告
void handleMemoryPressure() {
    super.handleMemoryPressure();
    imageCache?.clear();
  }
複製程式碼

圖片儲存非常耗記憶體,所以當App記憶體警告時需要清除掉快取。

ServicesBinding

ServicesBinding的主要功能是接收MethodChannelSystemChannels傳遞過來的訊息。我們來看看ServicesBinding的主要程式碼:

  • initInstances初始化方法
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();
}
複製程式碼
  1. createBinaryMessenger()建立了一個MethodChannel
  2. createRestorationManager()建立了一個RestorationManager用於恢復介面資料的功能;

這個場景主要是手機App進入後臺後可能已經被殺死(釋放資源給其他App在前臺流程執行),可以通過恢復資料在App切換回來的時候,讓使用者感覺手機App一直在後臺執行的假象;

  1. 通過第一步建立的_defaultBinaryMessenger實現和Plugin外掛的通訊
  2. initLicenses是給一些檔案加上Licenses說明;
  3. 接收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;
  }
複製程式碼
  1. 讀取當前的生命週期狀態,處理則是在父類SchedulerBinding這個mixin中去實現的。

SchedulerBinding

SchedulerBinding主要處理任務排程。在Flutter中有幾個排程階段:

  1. idle

這個階段沒有繪製幀任務處理,主要處理TaskMicrotaskTimer回撥,使用者輸入和手勢,以及其他一些任務。

  1. transientCallbacks

這個階段主要處理動畫狀態的計算和更新

  1. midFrameMicrotasks

這個階段處理transientCallbacks階段觸發的Microtasks

  1. persistentCallbacks

這個階段主要處理build/layout/paint,在RendererBinding那部分有提到

  1. postFrameCallbacks

這個階段主要在下一幀之前,做一些清理工作或者準備工作

接下來我們看看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;
    }
}
複製程式碼
void _setFramesEnabledState(bool enabled) {
    if (_framesEnabled == enabled)
      return;
    _framesEnabled = enabled;
    if (enabled)
      scheduleFrame();
}
複製程式碼

監聽生命週期變化,生命週期的狀態改變設定_framesEnabled的值,如果_framesEnabledfalse停止重新整理介面;如果_framesEnabledtrue呼叫scheduleFrameNative Platform請求重新整理檢視的請求。

  • scheduleFrame
void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    // 1  
    ensureFrameCallbacksRegistered();
    // 2
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
複製程式碼
  1. ensureFrameCallbacksRegistered()是先確保向window註冊了onBeginFrameonDrawFrame兩個重要回撥函式;
void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
}
複製程式碼
  1. window.scheduleFrame()是向Native platform發起一個重新整理檢視的請求;傳送這個請求後,Native platform會在合適的時間呼叫onBegineFrameonDrawFrame這兩個函式, 這兩個回撥會完成重新整理檢視所需的操作,比如更新widgets、動畫、和完成渲染等。這些都完成後再呼叫window.scheduleFrame(),一直迴圈下去,直到程式退出前臺或者程式退出。
  • 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;
    }
}
複製程式碼
Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
複製程式碼

handleBeginFrame的功能是執行_transientCallbacks中的所有函式。向transientCallbacks中新增回撥主要是Ticker.scheduleTick方法,是動畫框架的一部分。

<!-- 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;
}
複製程式碼
  • 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;
    }
}
複製程式碼
final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];  
複製程式碼

handleDrawFrame中執行了兩種回撥函式,persistentCallbackspostFrameCallbacks中所有的回撥函式。

  • Tasks相關的程式碼
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;
}

複製程式碼

task就是自定義的一些任務。task相關的有好幾個方法,其實邏輯也很清晰,總結如下:

  1. 所有的task放在HeapPriorityQueue中,這個執行的優先順序比動畫的優先順序低,保證瞭如果有動畫就不會執行這些task, 確保動畫的流程。
  2. 在非渲染階段,Task按照優先順序從高到低一個個執行,直到都執行完畢。

如果需要較快執行,可以使用FutureIsolate等。

runapp函式中的scheduleWarmUpFrame就是呼叫的SchedulerBinding的方法,後面單獨列出來說明。

GestureBinding

GestureBinding主要處理使用者的各種操作:

  • initInstances初始化方法
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
    void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
}
複製程式碼

GestureBinding_handlePointerDataPacket來處理windowonPointerDataPacket方法,這個是事件的入口。

  • _handlePointerDataPacket的事件處理方法流程
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);
    }
}
複製程式碼

_handlePointerDataPacket通過一系列的方法呼叫,最後呼叫_handlePointerEventImmediately方法。

  1. eventPointerDownEvent或者PointerHoverEvent時,新建一個HitTestResult物件,它有一個path屬性,用來記錄事件傳遞所經過的的節點。
  2. HitTestResultGestureBinding也加在了path中。
void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
}
複製程式碼
  1. 如果eventPointerDownEvent,將這個event加入到_hitTests中, 為了在event.down-即移動的時候也能獲取到它。
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
複製程式碼
  1. eventPointerUpEvent或者PointerCancelEvent時,將這個event_hitTests中移除。
  2. 最後呼叫dispatchEvent(event, hitTestResult)方法。
  • dispatchEvent方法

如果您有印象,RendererBinding中我們提到過dispatchEvent方法。

<!-- rendererBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    _mouseTracker!.updateWithEvent(event,
          () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
    super.dispatchEvent(event, hitTestResult);
}    
複製程式碼

其中重要的呼叫邏輯renderView.hitTestMouseTrackers(event.position)),會從renderview一直遍歷它的child,將沿途的Widget加入到path中。

程式碼如下:

<!-- 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;
  }
複製程式碼

當遍歷完renderView的所有widget後,將hitTestResult返回給GestureBindingdispatchEvent方法,然後遍歷path陣列,逐個呼叫handleEvent方法。

<!-- 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);
    }
}
複製程式碼

handleEvent方法最後就做了一些路由和手勢的處理等。

事件處理的鏈路介紹完畢。

WidgetsBinding

WidgetsBinding主要處理widget tree的一些邏輯:

  • initInstances初始化方法
void initInstances() {
    super.initInstances();
    _instance = this;

    // 1
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    // 2
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
}
複製程式碼
  1. 初始化了一個BuildOwner物件,它主要是執行widget treebuild任務;
  2. 執行了一些window的回撥。

至此,第一步WidgetsFlutterBinding.ensureInitialized()所涉及的知識點已經詳細的介紹完畢了。接下來我們來進入第二個階段。

scheduleAttachRootWidget

ensureInitialized的介紹做了很多擴充套件,讓大家對框架有了一個整體的認識。scheduleAttachRootWidget這一步我們只沿著程式碼流程一步步介紹。

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>?);
}
複製程式碼
  • scheduleAttachRootWidget非同步呼叫了attachRootWidget方法。
  1. attachRootWidget中初始化了一個RenderObjectToWidgetAdapter物件,建構函式傳入了renderViewrootWidgetrenderView就是RendererBindinginitInstances方法中初始化的那個物件,rootWidget則是我們寫的介面MyApp()

從建構函式的引數名我們可以看到,renderView是容器,rootWidget是這個容器的child。也就是說renderView是所有的Widget的根。

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
複製程式碼

吐槽:RenderObjectToWidgetAdapter其實就是一個RenderObjectWidget子類,加個Adapter有點讓人誤解。

  1. RenderObjectToWidgetAdapter物件呼叫attachToRenderTree方法,把構造的工具**_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!;
}
複製程式碼
  1. 建立了一個RenderObjectElement的子類RenderObjectToWidgetElement,並將構造工具buildOwner引用給了它;
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
複製程式碼
  1. element呼叫mount方法。
  2. 先提前告訴Native platform想要重新整理介面。
  • 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();
  }
複製程式碼
  1. RenderObjectToWidgetElementmount方法先呼叫Elementmount方法。主要的作用就是設定_parent,_slot,_owner_depth等的值;

簡單介紹下:_parent就是Element樹上的父節點,_slot是插槽,可以簡單理解為子Element在父節點的位置,_depth是在Element tree上的深度。

  1. 然後呼叫RenderObjectElementmount方法。建立了一個renderObject,其實就是renderView。然後把這個renderObject掛載到RenderObject Tree上,之前的RenderObject Tree沒有內容,所以renderView就是根節點;

Flutter有三棵樹,Widget tree, Element TreeRenderObject TreeRenderObject Tree是真正渲染出來的內容。

  • RenderObjectToWidgetElement _rebuild
void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
    } catch (exception, stack) {
    }
}
複製程式碼

_rebuild的功能就是BuildWidget,這裡就是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 {
      // 建立Element
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }
複製程式碼

updateChild中如果child為null,newWidget不為null, 則會呼叫newChild = inflateWidget(newWidget, newSlot);

Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }
複製程式碼

inflateWidget先建立一個Element,然後這個Element呼叫mount方法。

又一個用mount方法,你猜對了, 使用buildOwnerWidget 樹---renderview->MyApp->MaterialApp... 一直Build下去,直到遍歷完成。

mount迴圈

Tree

scheduleWarmUpFrame

scheduleWarmUpFrameSchedulerBinding的方法:

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

    lockEvents(() async {
      await endOfFrame;
    });
}
複製程式碼

scheduleWarmUpFrame就是呼叫handleBeginFramehandleDrawFrame方法繪製一幀呈遞給GPU去顯示。

這裡需要說明的是scheduleWarmUpFrame是立即去繪製的,沒有等待Vsyn的通知,因為啟動的顯示要越快越好。

後面的lockEvents也是為了等待預約幀繪製完成後再去執行其他的任務。

繪製的是什麼?繪製的是RenderObject Tree對應的Layer Tree,最後以Scene的形式呈遞給GPU顯示。

相關文章