載入順序、效能和記憶體
本頁說明顯示 Flutter UI 所涉及的步驟分解。了解這些步驟後,您可以更明智地決定何時預熱 Flutter 引擎、哪些操作可以在哪個階段進行,以及這些操作的延遲和記憶體成本。
載入 Flutter
#Android 和 iOS 應用程式(整合到現有應用程式的兩個支援平台)、完整的 Flutter 應用程式和加入應用程式模式在顯示 Flutter UI 時,都有類似的概念性載入步驟順序。
尋找 Flutter 資源
#Flutter 的引擎執行時和您的應用程式編譯後的 Dart 程式碼都以共享函式庫的形式捆綁在 Android 和 iOS 上。載入 Flutter 的第一步是在您的 .apk/.ipa/.app 中找到這些資源(以及其他 Flutter 資產,例如圖片、字體和 JIT 程式碼,如果有的話)。
當您在 Android 和 iOS 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 上的 Surface
或 iOS 上的 CAEAGLLayer 或 CAMetalLayer。
此時,您的 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。
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-11-19。 查看原始碼 或 回報問題。