Flutter 效能分析
診斷 Flutter 中的 UI 效能問題。
概述
#應用效能涵蓋了多個方面,從原始速度和 I/O 吞吐量到使用者介面的流暢度。雖然本頁主要關注 UI 流暢度(避免卡頓或掉幀),但此處介紹的工具通常也可用於診斷其他效能問題。
Flutter 提供了多種效能分析工具。以下是其中的一些:
-
效能圖層 (Performance Overlay):直接在執行的應用內顯示一組簡化的指標。要了解更多資訊,請參閱本主題中的相關章節。
-
效能檢視 (Performance View):一個基於 Web 的介面,可連線到你的應用並顯示詳細的效能指標。它是 DevTools 工具集的一部分。要了解更多資訊,請參閱 使用效能檢視。
-
Dart 中的效能追蹤:使用
dart:developer包直接在應用的 Dart 程式碼中新增追蹤,然後在 DevTools 工具中跟蹤應用的效能。要了解更多資訊,請參閱 追蹤 Dart 程式碼效能。 -
基準測試 (Benchmarking):你可以透過編寫基準測試來衡量和跟蹤應用的效能。Flutter Driver 庫提供了對基準測試的支援。使用此整合測試框架,你可以生成跟蹤卡頓、下載大小、電池效率和啟動時間的指標。有關更多資訊,請檢視 整合測試。
-
Widget 重建分析器 (IntelliJ for Android Studio):卡頓通常源於不必要的 UI 重建。如果你使用的是 IntelliJ 或 Android Studio,Widget 重建分析器可以透過顯示當前螢幕和幀的 widget 重建次數,幫助定位並修復這些問題。有關更多資訊,請參閱 顯示效能資料。
Flutter 旨在提供每秒 60 幀 (fps) 的效能,在支援的裝置上可達 120 fps。為了達到 60fps,每一幀必須在大約 16ms 內完成渲染,以避免卡頓。當渲染一幀的時間過長導致該幀被丟棄時,就會發生卡頓,從而在動畫中出現明顯的停頓。例如,如果某一幀偶爾需要比平時長 10 倍的時間來渲染,它很可能會被丟棄,導致動畫看起來不連貫。
連線到物理裝置
#幾乎所有的 Flutter 應用效能除錯都應該在物理 Android 或 iOS 裝置上進行,並將 Flutter 應用執行在 profile 模式下。使用 debug 模式,或在模擬器上執行應用,通常無法代表釋出模式構建的最終表現。你應該考慮在使用者可能使用的最慢的裝置上檢查效能。
在效能分析模式下執行
#Flutter 的 profile 模式編譯和啟動應用的方式幾乎與 release 模式完全相同,但僅包含足夠的額外功能以允許除錯效能問題。例如,profile 模式為效能分析工具提供了追蹤資訊。
按如下方式在 profile 模式下啟動應用:
-
在 VS Code 中,開啟
launch.json檔案,將flutterMode屬性設定為profile(分析完成後,改回release或debug)。json"configurations": [ { "name": "Flutter", "request": "launch", "type": "dart", "flutterMode": "profile" } ] -
在 Android Studio 和 IntelliJ 中,使用 Run > Flutter Run main.dart in Profile Mode 選單項。
-
在命令列中,使用
--profile標誌。flutter run --profile
有關不同模式的更多資訊,請參閱 Flutter 的構建模式。
你將從開啟 DevTools 並檢視效能圖層開始,詳見下一節。
啟動 DevTools
#DevTools 提供了效能分析、堆記憶體檢查、顯示程式碼覆蓋率、啟用效能圖層以及分步偵錯程式等功能。DevTools 的時間軸檢視 (Timeline view) 允許你逐幀調查應用的 UI 效能。
一旦應用在 profile 模式下執行,請啟動 DevTools。
顯示效能圖層
#你可以按如下方式切換效能圖層的顯示:
-
DevTools 效能檢視:啟用 PerformanceOverlay widget 的最簡單方法是透過 DevTools 中的 效能檢視。只需點選 Performance Overlay 按鈕即可切換正在執行的應用上的圖層顯示。
-
命令列:在命令列中使用 P 鍵切換效能圖層。
-
以程式設計方式:要以程式設計方式啟用圖層,請參閱 以程式設計方式除錯 Flutter 應用 頁面中的 效能圖層 一節。
觀察效能圖層
#效能圖層在兩個圖表中顯示統計資訊,展示應用的時間消耗情況。如果 UI 出現卡頓(掉幀),這些圖表可以幫助你找出原因。圖表顯示在執行應用的正上方,但它們不是作為普通 widget 繪製的——Flutter 引擎本身會繪製該圖層,對效能的影響極小。每個圖表代表該執行緒最近的 300 幀。
本節介紹如何啟用效能圖層並利用它診斷應用中卡頓的原因。以下螢幕截圖顯示了在 Flutter Gallery 示例上執行的效能圖層:
效能圖層顯示光柵執行緒 (raster thread,上方) 和 UI 執行緒 (下方)。
垂直的綠色條代表當前幀。
檢視圖表
#上方圖表(標記為 "GPU")顯示了光柵執行緒消耗的時間,下方圖表顯示了 UI 執行緒消耗的時間。圖表中的白色橫線表示縱軸上 16ms 的增量;如果圖表超過了這些線,說明你的執行速度低於 60Hz。橫軸代表幀。圖表僅在應用重繪時更新,因此如果應用處於空閒狀態,圖表將停止移動。
圖層應始終在 profile 模式下檢視,因為 debug 模式的效能為了支援有助於開發的昂貴斷言而被故意犧牲,因此結果具有誤導性。
每一幀都應在 1/60 秒(約 16ms)內完成建立和顯示。超過此限制的幀(在任一圖表中)將無法顯示,導致卡頓,並在一個或兩個圖表中出現垂直的紅色條。如果 UI 圖表中出現紅條,說明 Dart 程式碼開銷過大。如果 GPU 圖表中出現垂直紅條,說明場景過於複雜,無法快速渲染。
垂直的紅色條表示當前幀的渲染和繪製開銷過大。
當兩個圖表都顯示紅色時,請從診斷 UI 執行緒開始。
檢視執行緒
#Flutter 使用多個執行緒來執行其工作,儘管圖層中僅顯示了兩個執行緒。你所有的 Dart 程式碼都執行在 UI 執行緒上。雖然你無法直接訪問任何其他執行緒,但在 UI 執行緒上的操作會對其他執行緒產生效能影響。
- 平臺執行緒 (Platform thread)
-
平臺的的主執行緒。外掛程式碼在此執行。有關詳細資訊,請參閱 iOS 的 UIKit 文件或 Android 的 MainThread 文件。該執行緒未在效能圖層中顯示。
- UI 執行緒 (UI thread)
-
UI 執行緒在 Dart VM 中執行 Dart 程式碼。該執行緒包含你編寫的程式碼,以及 Flutter 框架代表你的應用執行的程式碼。當你的應用建立並顯示一個場景時,UI 執行緒會建立一個 圖層樹 (layer tree)——一個包含裝置無關繪製指令的輕量級物件,並將該圖層樹傳送給光柵執行緒以在裝置上渲染。不要阻塞此執行緒! 在效能圖層的底部行顯示。
- 光柵執行緒 (Raster thread)
-
光柵執行緒接收圖層樹並透過與 GPU(圖形處理單元)通訊來顯示它。你無法直接訪問光柵執行緒或其資料,但如果該執行緒執行緩慢,通常是因為你在 Dart 程式碼中所做的某些操作導致的。圖形庫 Skia 和 Impeller 執行在此執行緒上。在效能圖層的頂部行顯示。請注意,雖然光柵執行緒為 GPU 進行光柵化,但執行緒本身執行在 CPU 上。
- I/O 執行緒 (I/O thread)
-
執行昂貴的任務(主要是 I/O),這些任務否則會阻塞 UI 或光柵執行緒。該執行緒未在效能圖層中顯示。
有關更多資訊和影片連結,請參閱 Flutter Wiki 中的 框架架構 (The Framework architecture),以及社群文章 分層蛋糕 (The Layer Cake)。
識別問題
#檢視 UI 圖表
#如果效能圖層在 UI 圖表中顯示紅色,請從分析 Dart VM 開始,即使 GPU 圖表也顯示紅色。
檢視 GPU 圖表
#有時,一個場景產生的圖層樹雖然容易構建,但在光柵執行緒上渲染卻非常耗時。出現這種情況時,UI 圖表沒有紅色,但 GPU 圖表顯示紅色。在這種情況下,你需要查明程式碼中是什麼導致渲染過程變慢。某些特定的工作負載對 GPU 來說更困難。它們可能涉及對 saveLayer 的不必要呼叫、多個物件的不透明度重疊,以及在特定情況下的裁剪或陰影。
如果你懷疑緩慢的來源是動畫,請點選 Flutter 檢查器 (Inspector) 中的 Slow Animations 按鈕,將動畫速度降低 5 倍。如果你想更精確地控制速度,也可以以程式設計方式執行此操作。
緩慢發生在第一幀,還是整個動畫過程中?如果是整個動畫,是否是裁剪導致了變慢?也許有替代的繪圖方法不需要裁剪。例如,在正方形上覆蓋不透明的角,而不是裁剪成圓角矩形。如果是一個正在淡出、旋轉或以其他方式操作的靜態場景,RepaintBoundary 可能會有幫助。
檢查螢幕外圖層 (offscreen layers)
#saveLayer 方法是 Flutter 框架中最昂貴的方法之一。它在對場景進行後期處理時很有用,但它會拖慢應用速度,如果不需要,應避免使用。即使你沒有顯式呼叫 saveLayer,系統也可能代表你進行隱式呼叫,例如在指定 Clip.antiAliasWithSaveLayer(通常作為 clipBehavior)時。
例如,假設你有一組使用 saveLayer 渲染的不透明物件。在這種情況下,將不透明度分別應用於每個單獨的 widget,而不是應用於 widget 樹中更上層的父 widget,通常效能會更好。其他潛在的昂貴操作(如裁剪或陰影)也是如此。
當你遇到對 saveLayer 的呼叫時,問問自己以下問題:
- 應用需要這個效果嗎?
- 這些呼叫中有任何可以消除的嗎?
- 我可以將同樣的效果應用到單個元素而不是一組元素上嗎?
檢查非快取影像
#使用 RepaintBoundary 快取影像是好的,前提是它有意義。
從資源角度來看,最昂貴的操作之一是渲染包含影像檔案的紋理。首先,壓縮後的影像從持久儲存中獲取。然後解壓縮到主機記憶體(GPU 記憶體)中,最後傳輸到裝置記憶體 (RAM) 中。
換句話說,影像 I/O 可能很昂貴。快取提供了複雜層級的快照,以便在後續幀中更容易渲染。因為光柵快取條目構建成本高昂且佔用大量 GPU 記憶體,所以僅在絕對必要時才快取影像。
其他資源
#以下資源提供了有關使用 Flutter 工具和在 Flutter 中進行除錯的更多資訊:
- 除錯
- 效能檢視
- Flutter 檢查器
- Flutter 檢查器講座,於 DartConf 2018 發表
- 為什麼 Flutter 使用 Dart,Hackernoon 上的文章
- 為什麼 Flutter 使用 Dart,Flutter 頻道上的影片
- DevTools:Dart 和 Flutter 應用的效能工具
-
Flutter API 文件,特別是
PerformanceOverlay類和dart:developer包