跳至主要內容

載入順序、效能和記憶體

本頁說明顯示 Flutter UI 所涉及的步驟分解。了解這些步驟後,您可以更明智地決定何時預熱 Flutter 引擎、哪些操作可以在哪個階段進行,以及這些操作的延遲和記憶體成本。

載入 Flutter

#

Android 和 iOS 應用程式(整合到現有應用程式的兩個支援平台)、完整的 Flutter 應用程式和加入應用程式模式在顯示 Flutter UI 時,都有類似的概念性載入步驟順序。

尋找 Flutter 資源

#

Flutter 的引擎執行時和您的應用程式編譯後的 Dart 程式碼都以共享函式庫的形式捆綁在 Android 和 iOS 上。載入 Flutter 的第一步是在您的 .apk/.ipa/.app 中找到這些資源(以及其他 Flutter 資產,例如圖片、字體和 JIT 程式碼,如果有的話)。

當您在 AndroidiOS API 上首次建構 FlutterEngine 時,就會發生這種情況。

載入 Flutter 函式庫

#

找到後,引擎的共享函式庫會在每個程序中載入一次到記憶體中。

Android 上,當建構 FlutterEngine 時也會發生這種情況,因為 JNI 連接器需要參考 Flutter C++ 函式庫。在 iOS 上,當 FlutterEngine 首次執行時發生,例如透過執行 runWithEntrypoint:

啟動 Dart VM

#

Dart 執行時負責管理您 Dart 程式碼的 Dart 記憶體和並行。在 JIT 模式下,它還負責在執行時將 Dart 原始碼編譯為機器碼。

在 Android 和 iOS 上,每個應用程式會期程都存在一個 Dart 執行時。

Android 上首次建構 FlutterEngine 時,以及在 iOS 上首次執行 Dart 入口點時,會執行一次 Dart VM 啟動。

此時,您的 Dart 程式碼的快照也會從您的應用程式檔案載入到記憶體中。

如果您直接使用 Dart SDK,而不使用 Flutter 引擎,也會發生這種通用程序。

Dart VM 在啟動後永遠不會關閉。

建立並執行 Dart Isolate

#

初始化 Dart 執行時後,下一步是 Flutter 引擎如何使用 Dart 執行時。

這是透過在 Dart 執行時中啟動一個 Dart Isolate 來完成的。Isolate 是 Dart 用於記憶體和執行緒的容器。此時也會建立主機平台上的一些輔助執行緒來支援 Isolate,例如用於卸載 GPU 處理的執行緒和用於圖片解碼的另一個執行緒。

每個 FlutterEngine 實例存在一個 Isolate,而同一個 Dart VM 可以託管多個 Isolate。

Android 上,當您在 FlutterEngine 實例上呼叫 DartExecutor.executeDartEntrypoint() 時,就會發生這種情況。

iOS 上,當您在 FlutterEngine 上呼叫 runWithEntrypoint: 時,就會發生這種情況。

此時,您的 Dart 程式碼所選的入口點(預設情況下,您 Dart 函式庫的 main.dart 檔案的 main() 函式)將會執行。如果您在 main() 函式中呼叫了 Flutter 函式 runApp(),則也會建立和建構您的 Flutter 應用程式或您函式庫的 widget 樹。如果您需要防止某些功能在您的 Flutter 程式碼中執行,則 AppLifecycleState.detached 列舉值會指示 FlutterEngine 未連接到任何 UI 元件,例如 iOS 上的 FlutterViewController 或 Android 上的 FlutterActivity

將 UI 連接到 Flutter 引擎

#

標準、完整的 Flutter 應用程式會在應用程式啟動後立即達到此狀態。

在加入應用程式的情境中,當您將 FlutterEngine 連接到 UI 元件時,就會發生這種情況,例如,透過在 Android 上使用 FlutterActivity.withCachedEngine() 建構的 Intent 呼叫 startActivity()。 或者,透過在 iOS 上使用 initWithEngine: nibName: bundle: 初始化的 FlutterViewController

如果 Flutter UI 元件是在沒有預熱 FlutterEngine 的情況下啟動的,例如使用 Android 上的 FlutterActivity.createDefaultIntent(),或使用 iOS 上的 FlutterViewController initWithProject: nibName: bundle:,情況也是如此。在這些情況下,會隱式建立 FlutterEngine

在幕後,這兩個平台的 UI 元件都為 FlutterEngine 提供了一個渲染表面,例如 Android 上的 SurfaceiOS 上的 CAEAGLLayerCAMetalLayer

此時,您的 Flutter 程式碼根據每個影格產生的 Layer 樹會轉換為 OpenGL (或 Vulkan 或 Metal) GPU 指令。

記憶體和延遲

#

顯示 Flutter UI 具有一定的延遲成本。可以透過提前啟動 Flutter 引擎來減少此成本。

對於加入應用程式的情境,最相關的選擇是讓您決定何時預先載入 FlutterEngine(也就是載入 Flutter 函式庫、啟動 Dart VM,並在 Isolate 中執行入口點),以及預熱的記憶體和延遲成本是多少。您還需要知道預熱如何影響當 UI 元件隨後連接到該 FlutterEngine 時,渲染第一個 Flutter 影格的記憶體和延遲成本。

根據 Flutter v1.10.3 的情況,並在低階 2015 級裝置上的 AOT 發布模式中進行測試,預熱 FlutterEngine 的成本為

  • Android 上預熱需要 42 MB 和 1530 毫秒。其中 330 毫秒是在主執行緒上的阻塞呼叫。
  • iOS 上預熱需要 22 MB 和 860 毫秒。其中 260 毫秒是在主執行緒上的阻塞呼叫。

可以在預熱期間附加 Flutter UI。剩餘的時間會加到第一個影格的延遲時間中。

在記憶體方面,成本樣本(可變,取決於使用案例)可能是

  • ~4 MB 作業系統的記憶體使用量用於建立 pthreads。
  • ~10 MB GPU 驅動程式記憶體。
  • ~1 MB Dart 執行時管理的記憶體。
  • ~5 MB Dart 載入的字體對應。

在延遲方面,成本樣本(可變,取決於使用案例)可能是

  • ~20 毫秒從應用程式套件收集 Flutter 資產。
  • ~15 毫秒 dlopen Flutter 引擎函式庫。
  • ~200 毫秒建立 Dart VM 並載入 AOT 快照。
  • ~200 毫秒載入 Flutter 相依的字體和資產。
  • ~400 毫秒執行入口點、建立第一個 widget 樹,並編譯所需的 GPU 着色器程式。

FlutterEngine 應該預熱到足夠晚,以延遲所需的記憶體消耗,但要足夠早,以避免將 Flutter 引擎啟動時間與顯示 Flutter 的第一個影格延遲結合在一起。

確切的時間取決於應用程式的結構和啟發式。一個範例是在 Flutter 繪製螢幕之前,先在螢幕中載入 Flutter 引擎。

在引擎預熱的情況下,UI 附加的第一個影格成本為

  • Android 上為 320 毫秒,外加 12 MB(高度取決於螢幕的實體像素大小)。
  • iOS 上為 200 毫秒,外加 16 MB(高度取決於螢幕的實體像素大小)。

在記憶體方面,成本主要是用於渲染的圖形記憶體緩衝區,並且取決於螢幕大小。

在延遲方面,成本主要是等待作業系統回呼,以向 Flutter 提供渲染表面,並編譯無法預先預測的剩餘着色器程式。這是一次性的成本。

當 Flutter UI 元件發佈時,會釋放與 UI 相關的記憶體。這不會影響 Flutter 狀態,該狀態存在於 FlutterEngine 中(除非 FlutterEngine 也被釋放)。

有關建立多個 FlutterEngine 的效能詳細資訊,請參閱多個 Flutter