載入順序、效能和記憶體
本頁介紹顯示 Flutter UI 所涉及的步驟。瞭解這些步驟,可以幫助您在預熱 Flutter 引擎的時機、各階段可行的操作以及這些操作的延遲和記憶體成本等方面做出更明智的決策。
載入 Flutter
#Android 和 iOS 應用(整合到現有應用支援的兩個平臺)、完整的 Flutter 應用以及新增到應用模式(add-to-app patterns)在顯示 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 記憶體和併發性。在 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** 上呼叫 startActivity() 並使用 FlutterActivity.withCachedEngine() 構建的 Intent。或者,在 **iOS** 上,透過呈現使用 initWithEngine: nibName: bundle: 初始化過的 FlutterViewController。
如果沒有預熱 FlutterEngine 而啟動 Flutter UI 元件(例如在 **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 引擎可以減輕這種成本。
對於新增到應用場景(add-to-app scenarios),最相關的選擇是決定何時預載入 FlutterEngine(即載入 Flutter 庫、啟動 Dart VM,並在 Isolate 中執行入口點),以及預熱的記憶體和延遲成本是多少。您還需要了解預熱如何影響 UI 元件隨後附加到該 FlutterEngine 時首次渲染 Flutter 幀的記憶體和延遲成本。
截至 Flutter v1.10.3,並在低端 2015 年類裝置上以 release-AOT 模式進行測試,預熱 FlutterEngine 的成本為:
- **Android** 上預熱成本為 42 MB 和 1530 ms。其中 330 ms 是主執行緒的阻塞呼叫。
- **iOS** 上預熱成本為 22 MB 和 860 ms。其中 260 ms 是主執行緒的阻塞呼叫。
可以在預熱期間附加 Flutter UI。剩餘時間將計入首次幀延遲。
記憶體方面,成本樣本(因用例而異)可能包括:
- ~4 MB OS 記憶體使用量,用於建立 pthreads。
- ~10 MB GPU 驅動程式記憶體。
- ~1 MB 用於 Dart 執行時管理的記憶體。
- ~5 MB 用於 Dart 載入的字型對映。
延遲方面,成本樣本(因用例而異)可能包括:
- ~20 ms 用於從應用程式包收集 Flutter 資源。
- ~15 ms 用於 dlopen Flutter 引擎庫。
- ~200 ms 用於建立 Dart VM 並載入 AOT 快照。
- ~200 ms 用於載入 Flutter 依賴的字型和資源。
- ~400 ms 用於執行入口點、建立第一個 widget 樹以及編譯所需的 GPU 著色器程式。
FlutterEngine 應在足夠晚的時間進行預熱,以延遲所需的記憶體消耗,但又足夠早,以避免將 Flutter 引擎的啟動時間與顯示 Flutter 的首次幀延遲合併。
確切的時間取決於應用的結構和啟發式方法。例如,可以在 Flutter 繪製螢幕之前,在前一個螢幕中載入 Flutter 引擎。
考慮到引擎預熱,UI 附加時的首次幀成本為:
- **Android** 上為 320 ms,額外增加 12 MB(高度依賴於螢幕的物理畫素尺寸)。
- **iOS** 上為 200 ms,額外增加 16 MB(高度依賴於螢幕的物理畫素尺寸)。
記憶體方面,成本主要是在渲染中使用的圖形記憶體緩衝區,並且取決於螢幕尺寸。
延遲方面,成本主要是等待作業系統回撥向 Flutter 提供渲染表面以及編譯剩餘的、無法預先預測的著色器程式。這是一個一次性成本。
當 Flutter UI 元件被釋放時,與 UI 相關的記憶體將被釋放。這不會影響生活在 FlutterEngine 中的 Flutter 狀態(除非 FlutterEngine 也被釋放)。
有關建立多個 FlutterEngine 的效能詳細資訊,請參閱 multiple Flutters。