flutter 應用啟動流程

無若葉發表於2020-03-06

Flutter 應用啟動流程

在 Flutter 專案中,應用的啟動一般是從 main.dart 開始的,

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

App 就是應用的主頁面,本質上是一個 Widget,換句話說,在 Flutter 中,萬物皆為 Widget,它由開發者編寫,runApp 則負責將這個 Widget 執行起來,展示到手機介面上,還有一系列的初始化工作,為之後的持續工作、使用者互動做準備。瞭解 flutter 應用的啟動過程,可以從這個函式開始。

runApp

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

從這裡可以判斷,runApp 大致有三個過程:

  1. 進行 WidgetsFlutterBinding 的初始化
  2. 將使用者介面 app 載入到 flutter 的 widget 樹中,這有點類似於 Android 中的 setContentView
  3. 執行繪製工作,將 widget 樹轉變為畫面

WidgetsFlutterBinding

WidgetsFlutterBinding 本身沒有什麼功能,只不過它整合了諸多 binding 類

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() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}
複製程式碼

依次是 BindingBase、GestureBinding、ServicesBinding、SchedulerBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding,這裡又涉及到 mixin 語法相關的知識,具體的這裡不闡述,這種結果導致的結果就是 WidgetsFlutterBinding 擁有以上幾個類所有的功能,而涉及到的函式重寫相關的,如果按優先順序來算,那就是呼叫函式時,先從 WidgetsBinding 中找是否有實現,如果沒有,再從 RendererBinding 中找,以此類推。然後再是 super 的使用,WidgetsBinding 中呼叫 super 函式會先從 RenderBinding 中找,RenderBinding 呼叫到 super 會先從 SemanticsBinding 中找,以此類推。

然後, WidgetsFlutterBinding 沒有建構函式,直接看從父類的,在 BindingBase 的建構函式中呼叫了 initInstances 這個函式,它在後面的每一個 binding 中都有實現,所以按照 mixin 的邏輯,應該先呼叫 WidgetsBinding 的實現,而在這幾個 binding 中都有呼叫 super.initInstances ,所以它會按照從 WidgetsBinding 至 BindingBase 的順序依次呼叫。

WidgetsBinding

分別向 BuildOwner 註冊了 onBuildScheduled 函式,向 Window 註冊了 onLocaleChanged 和 onAccessibilityFeaturesChanged 函式,向 SystemChannels 註冊了 MethodCallHandler 和 MessageHandler 函式,後面兩個能夠接收傳遞過來的路由訊息和系統訊息,如 popRoute、pushRoute、memoryPressure 等。

RendererBinding

RendererBinding 中主要是與渲染相關的功能,初始化時建立了 PipelineOwner 例項,這個類就是負責介面渲染的,之後的 layout、paint 等過程 PipelineOwner 都會承擔著很多責任,然後又在 Window 中註冊了一些函式,接著初始化 RenderView ,還有就是一個比較重要的函式,_handlePersistentFrameCallback,這個函式被呼叫之後就會執行一系列的繪製操作,將 Widget 樹繪製到螢幕中。

還有就是初始化了 RenderView,並將其加入 _nodesNeedingLayout 和 _nodesNeedingPaint,最後呼叫 requestVisualUpdate 請求重新整理檢視。

renderView 的 set 函式會將其與 PipelineOwner 聯絡起來,呼叫其 attach 函式,RenderView 的父類 RenderObject 重寫了這個函式,將 RenderView 標記成需要 layout、compositingBitsUpdate、paint 和 semanticsUpdate。

SemanticsBinding

內部儲存有一個 AccessibilityFeatures 例項,從 Window 中取得,Window 中又是從 C++ 中傳遞過來的,AccessibilityFeatures 本質上就是一個 int 值,然後通過位運算得到配置資訊,如是否反轉顏色、是否禁止動畫、是否顯示粗體字等 Android、ios 中的系統配置資訊。

PaintingBinding

PaintingBinding 可以認為有兩個功能,一個是提供了 ImageCache 類,可以用於快取圖片,另一個是呼叫了 ShaderWarmUp 的 execute 函式,從意思上看是用於啟動 skia(底層圖形繪製庫)的著色器編譯,這一點可能是有預先啟動以防使用到再去啟動會影響等待時間的意味?

SchedulerBinding

SchedulerBinding 向 Window 註冊了兩個回撥函式,onBeginFrame 和 onDrawFrame ,這兩個函式就是在幀到來之後的回撥,包括 Widgets 樹的重新整理和繪製、動畫的處理以及其他使用者投放的任務,對於 Android 平臺來說,這兩個函式呼叫的時機就是 Choreographer 回撥之後,另外從功能上來說,它們也是與 Android 中基本一致的。

然後註冊了生命週期的回撥函式 _handleLifecycleMessage 。

ServicesBinding

向 Window 註冊了 onPlatformMessage 函式,後續的開發外掛時就是通過這個函式分發,然後每個外掛得以接收訊息。

GestureBinding

GestureBinding 向 Window 註冊了 onPointerDataPacket 函式,這個函式就是原生平臺上接收到觸控事件之後回撥的,也是 flutter 中觸控手勢的提供方,它會在接收到傳來的觸控事件之後,將其分發出去。

BindingBase

BindingBase 中則沒有實際的功能。

attachRootWidget

attachRootWidget 的引數是 app ,也就是在這個函式中,將使用者自定義的 widget 掛載到 flutter 應用的 widget 樹中,並生成 element 等,為下一步繪製做準備。

這裡首先建立了 RenderObjectToWidgetAdapter 物件,將 widget 作為其 child ,renderView 為 container ,renderView 是在 RendererBinding 初始化時建立的,也是整個渲染樹的根節點,然後呼叫 RenderObjectToWidgetAdapter 的 attachToRenderTree 函式,將會利用 widget 樹生成 element 樹。

在這個函式中先是建立了根 element ,然後呼叫 buildScope 和 mount 函式,mount 可以看作是生成 element 樹,buildScope 會將所有標記為 dirty 的 element 進行 rebuild,可以重新整理檢視。

buildScope

buildScope 會先呼叫 callback ,在這裡的 callback 就是 mount ,然後處理 _dirtyElements,先對其按照深度排序,再來一個 while 迴圈,呼叫每一個 dirty element 的 rebuild 函式,並且如果在處理的過程中發現 _dirtyElements 的數量發生變化或者需要重新處理的,就會將其重新排序,然後找到第一個 dirty element 繼續處理,完了之後就清空 _dirtyElements。

mount

RenderObjectToWidgetElement 的 mount 函式會呼叫 _rebuild,然後又會呼叫 updateChild 更新 child ,具體的更新策略就是:

  1. 如果新的 widget 為 null,直接返回 null
  2. child 為 null,呼叫 inflateWidget 生成 child
  3. child 不為 null
    1. 新的 widget 與舊 widget 相同,只需要更新下 slot
    2. 否則,判斷是否可以複用,呼叫 canUpdate 函式,可以則更新下 slot ,不可以則執行 inflateWidget 重新生成

第一次執行的時候,child 為 null,所以應該直接呼叫 inflateWidget 去生成。

inflateWidget

函式的引數為 widget 和 slot,首先會判斷 widget 的 key 是否為 GlobalKey,如果它是一個 GlobalKey,也就意味著這個 element 是可全域性唯一的,會先從 GlobalKey 中查詢是否有可複用,沒有現成的 element 時才會呼叫 createElement 生成 element,然後接著呼叫這個 element 的 mount 函式,與之前的 root element 不同,這裡的 element 對應的類一般是 ComponentElement 的子類,而 ComponentElement 有自己實現的 mount ,在它實現的 mount 中會依次呼叫到 _firstBuild,rebuild 和 performRebuild,在 performRebuild 中,先是生成了一個 widget 物件,接著又呼叫 updateChild 生成了 widget 對應的 element,由此形成一個呼叫鏈,updateChild 中會繼續呼叫 inflateWidget,直到整個 widget 樹轉化為 element 樹之後結束。

scheduleWarmUpFrame

element 樹生成之後,繪製的基礎就有了,下一步就是呼叫 scheduleWarmUpFrame 函式將這樣一個系統啟動執行。scheduleWarmUpFrame 會呼叫 handleBeginFrame 和 handleDrawFrame 函式開啟 flutter 應用的第一幀,後續的幀序列將會由 flutter 自行生成,在 Android 中它會輾轉呼叫到 Android 中的 Choreographer 生成下一幀,然後再通過 C++ 傳回 flutter 中,完成自給。

handleBeginFrame

在 handleBeginFrame 中主要是執行 _transientCallbacks,即呼叫 SchedulerBinding:scheduleFrameCallback 函式新增的回撥,比如 Ticker 中的 scheduleTick 就通過這個函式延遲呼叫 _tick 函式。

handleDrawFrame

handleDrawFrame 與 handleBeginFrame 類似,只不過執行了 _persistentCallbacks 和 _postFrameCallbacks 兩類回撥,前者為固有回撥,比如在 RendererBinding 初始化中新增的 _handlePersistentFrameCallback,後者為使用者需要在繪製之後執行的回撥。

綜上,在每一幀中需要處理三種回撥,除了 _persistentCallbacks 每一幀都要呼叫之外,其他的都是一次性的。

_handlePersistentFrameCallback

這個函式會呼叫 drawFrame 函式,drawFrame 在 WidgetsBinding 和 RendererBinding 中分別有實現,根據 mixin 機制,呼叫的應該是 WidgetsBinding 中的實現,其功能大致分為三部分:

  1. 呼叫 buildOwner.buildScope 重新整理 element 樹
  2. 呼叫 RendererBinding drawFrame 函式
  3. 呼叫 buildOwner.finalizeTree 清理無效 element

1 中之前有說到過,就是 rebuild 所有的 dirty element,3 中會以每一個 dirty element 為起點,呼叫 element 及其所有 child 的 unmount 函式。

drawFrame

drawFrame 這個函式完成了本地各 widget 的繪製,以及將其渲染到螢幕上的整個過程,共五個過程:

  1. flushLayout
  2. flushCompositingBits
  3. flushPaint
  4. compositeFrame
  5. flushSemantics

第四個是 RenderView 的函式,其他都是 PipelineOwner 的函式,PipelineOwner 在 RendererBinding 的初始化過程中被建立。

flushLayout

這個過程完成所有 widget 的佈局計算,通過呼叫所有需要重新佈局的結點的 _layoutWithoutResize 函式,這個函式又分為三個部分呼叫:

  1. performLayout
  2. markNeedsSemanticsUpdate
  3. markNeedsPaint

1 過程確定 widget 的大小和位置,2 過程將 widget _needsSemanticsUpdate 標記為 true,並從這個節點上溯到邊界節點,然後將這個節點入到 PipelineOwner 的 _nodesNeedingSemantics 中,並呼叫 requestVisualUpdate 函式,requestVisualUpdate 中呼叫的 onNeedVisualUpdate 在 PipelineOwnder 初始化時傳入,這個函式會在空閒狀態或 postFrameCallbacks 狀態請求下一幀,總之,2 過程的標記會影響 flushSemantics 過程的執行。3 過程則會從 widget 上溯到邊界節點,將這個節點加入到 _nodesNeedingPaint 中,這個過程會影響 flushPaint 的執行。

flushCompositingBits

更新節點的 composition bits。

flushPaint

遍歷所有需要繪製的節點,如果節點沒有掛載到 widget 樹中,則跳過,否則就呼叫 PaintingContext.repaintCompositedChild 函式,然後依次呼叫 _paintWithContext、paint 函式,paint 函式一般是 widget 自由實現,可以通過 PaintingContext 取得 Canvas ,並在 Canvas 上繪製任何內容,其使用與 Android 中的 draw 方法無二。

compositeFrame

將 compositing bits 傳送到 GPU。

flushSemantics

將 semantics 傳送到作業系統。

關於重新整理幀,可以以 setState 為例,看一下 flutter 中是如何從 Android 原生中獲取下一幀回撥的。

setState

開發過 flutter 都知道,當我們需要改變檢視的時候不能直接操作 Widget 或 Element ,而是改變資料,通過資料重新整理驅動 widget 樹重新整理,生成新的 element 樹,從而顯示出新的介面,而更新資料需要放在 setState 中,否則就會只有資料的更新,而不會帶動檢視更新,所以從這一點可以猜測,setState 中做了某些處理,以至最終呼叫到 drawFrame,從而重新整理檢視,那麼下面就分析下從呼叫 setState 到重新整理檢視的全過程。

setState 的引數是一個 callback,在 setState 中會先呼叫 callback ,然後接著呼叫 markNeedsBuild ,前者一般是開發者用於改變資料的,那麼就只能從 markNeedsBuild 入手。

markNeedsBuild

這個函式會將 element 標記為 dirty,然後呼叫 scheduleBuildFor 函式,它會呼叫 onBuildScheduled 函式,然後將 element 加入 _dirtyElements,同時標記 _inDirtyList 為 true。

onBuildScheduled

onBuildScheduled 是在 WidgetsBinding 初始化時註冊到 BuildOwner 中的,對應的回撥函式為 _handleBuildScheduled,這個函式會呼叫 ensureVisualUpdate,而 ensureVisualUpdate 又會根據當前的 SchedulerPhase 值選擇性操作,只有在 idle 和 postFrameCallbacks 狀態時才會呼叫 scheduleFrame ,其他狀態,比如 transientCallbacks、midFrameMicrotasks、persistentCallbacks 等,都被看作是一幀正在進行中,而目前對 element 樹的改動還是會被 drawFrame 處理到的,檢視會按照預期的重新整理,所以沒必要再去請求新的幀。

scheduleFrame

scheduleFrame 會呼叫 Window 的 scheduleFrame 函式,這是一個 native 函式,會進入到 C++ 中執行,此舉就是為了通過 C++ 繼而轉到 Android 程式碼中執行,這個函式會在 C++ 中依次呼叫 window、runtime_controller、engine 和 animator 中對應的函式,最終呼叫 Animator::AwaitVSyn 。

Animator::AwaitVSyn

void Animator::AwaitVSync() {
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](fml::TimePoint frame_start_time,
                                          fml::TimePoint frame_target_time) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree();
          } else {
            self->BeginFrame(frame_start_time, frame_target_time);
          }
        }
      });

  delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}
複製程式碼

在這個函式中呼叫了 VsyncWaiter::AsyncWaitForVsync 函式,並傳遞了一個 callback 函式,裡面會呼叫 BeginFrame,由此可知,這個 callback 就是在新的一幀到來時的回撥函式,接著再看 VsyncWaiter 中的,它先將 callback 儲存在成員變數中,然後呼叫了 AwaitVSync,以 Android 平臺為例,這個函式將會被它的子類 VsyncWaiterAndroid 實現,這個實現利用 jni 呼叫 java 中的方法 FlutterJNI.asyncWaitForVsync。

FlutterJNI.asyncWaitForVsync

在 java 中這個方法會呼叫 AsyncWaitForVsyncDelegate.asyncWaitForVsync 方法,這是一個介面,其實現如下:

private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() {
    @Override
    public void asyncWaitForVsync(long cookie) {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                float fps = windowManager.getDefaultDisplay().getRefreshRate();
                long refreshPeriodNanos = (long) (1000000000.0 / fps);
                FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
            }
        });
    }
};
複製程式碼

由此就可以找到 Choreographer 與 flutter 之間的聯絡,flutter 呼叫的 requestFrame 最終會向 Choreographer 註冊一個回撥,然後等待 Choreographer 下一幀的到來,再去執行 FlutterJNI.nativeOnVsync,可想而之,這個過程是一個從 java 呼叫到 flutter 的過程,與上面的相反。

FlutterJNI.nativeOnVsync

這是一個 native 函式,會對映到呼叫 VsyncWaiterAndroid::OnNativeVsync 函式,VsyncWaiterAndroid 本質上看就是一個 jni 的介面類,它負責從 C++ 中呼叫 java 方法,也負責被 java 方法呼叫,再傳遞給 Animator。在它的 OnNativeVsync 中會依次呼叫 ConsumePendingCallback、FireCallback,在 FireCallback 中會先拿到之前傳進來的 callback ,然後就是在 UI 執行緒中呼叫它,也就是 Animator::AwaitVSync 中宣告的 callback 函式,所以,下一步就是呼叫 BeginFrame。

Animator::BeginFrame

這個函式的主要實現就是呼叫 OnAnimatorBeginFrame,附加的還有如判斷當前管道中是有可用的 producer_continuation,以及在最後呼叫 OnAnimatorNotifyIdle。

Animator 的 delegate 是 Shell,Shell 會轉而呼叫 Engine 的 BeginFrame,Engine 再呼叫 RuntimeController 的 BeginFrame,然後是 Window 的 BeginFrame,Window 就是 C++ 中用於與 dart 互動的中間類,它負責從 C++ 中呼叫 dart 函式和 dart 對 C++ 函式的呼叫,在這裡,它會通過 DartInvokeField 先後呼叫 dart 中的 _beginFrame 和 _drawFrame。

hooks.dart

在 dart 中 hooks.dart 負責接收 C++ 層的呼叫,比如 _beginFrame 函式:

@pragma('vm:entry-point')
void _beginFrame(int microseconds) {
  _invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
}
複製程式碼

它再去呼叫 window 中的函式,而 window 中的 onBeginFrame、onDrawFrame 等函式都是在之前初始化 SchedulerBinding 時就註冊過的,就是在之前 scheduleWarmUpFrame 也會呼叫的 handleBeginFrame 和 handleDrawFrame。

由此,每當 flutter 中需要重新整理檢視時,就通過請求幀、回撥、處理幀這樣一個過程完成。

相關文章