Flutter 效能分析
有句話叫「快的應用固然很好,但流暢的應用則更好。」如果你的應用渲染並不流暢,該怎麼處理呢?從哪裡著手呢?本文展示了應該從哪裡著手,步驟以及可以提供幫助的工具。
分析效能問題
分析應用的效能問題需要開啟效能監控圖層 (performance overlay) 來觀察 UI 和 GPU 執行緒。在此之前,要確保是在 分析模式 下執行,而且當前裝置不是虛擬機器。使用使用者可能採用的最慢裝置來獲取最佳結果。
連線到物理裝置
幾乎全部的 Flutter 應用效能除錯都應該在真實的 Android 或者 iOS 裝置上以 分析模式 進行。通常來說,除錯模式或者是模擬器上執行的應用的效能指標和釋出模式的表現並不相同。 應該考慮在使用者使用的最慢的裝置上檢查效能。
在分析模式執行
除了一些除錯效能問題所必須的額外方法, Flutter 的分析模式和釋出模式的編譯和執行基本相同。例如,分析模式為分析工具提供了追蹤資訊。
使用分析模式執行應用的方法:
-
在 Android Studio 和 IntelliJ 使用 Run > Flutter Run main.dart in Profile Mode 選項
-
在 VS Code 中,開啟
launch.json
檔案,設定flutterMode
屬性為profile
(當分析完成後,改回release
或者debug
):"configurations": [ { "name": "Flutter", "request": "launch", "type": "dart", "flutterMode": "profile" } ]
-
命令列使用
--profile
引數執行$ flutter run --profile
關於不同模式的更多資訊,請參考文件: Flutter 的建構模式選擇。
下面我們會從開啟 DevTools、檢視效能圖層開始講述。
執行 DevTools
Dart DevTool 提供諸如效能分析、堆測試以及顯示程式碼覆蓋率等功能。 DevTool 的 [Timeline] 介面可以讓開發者逐幀分析應用的 UI 效能。
一旦你的應用程式在分析模式下執行,即 執行 DevTools。
效能圖層
效能圖層用兩張圖表顯示應用的耗時資訊。如果 UI 產生了卡頓(跳幀),這些圖表可以幫助分析原因。圖表在當前應用的最上層展示,但並不是用普通的 widget 方式繪製的—Flutter 引擎自身繪製了該圖層來儘可能減少對效能的影響。每一張圖表都代表當前執行緒的最近 300 幀表現。
本節闡述如何開啟效能圖層並用其來分析應用中卡頓的原因。下面的截圖展示了 Flutter Gallery 範例的效能圖層:
raster 執行緒的效能情況在上面,UI 執行緒顯示在下面。
垂直的綠色條條代表的是當前幀。
圖表解釋
最頂部(標誌了 “GPU”)的圖形表示 raster 執行緒所花費的時間,底部的圖表顯示了 UI 執行緒所花費的時間。橫跨圖表中的白線代表了 16 ms 內沿豎軸的增量;如果這些線在圖表中都沒有超過它的話,說明你的執行幀率低於 60 Hz。而橫軸則表示幀。只有當你的應用繪製時這個圖表才會更新,所以如果它空閒的話,圖表就不會動。
這個浮層只應在 分析模式 中使用,因為在 除錯模式 下有意犧牲了效能來換取昂貴的斷言以幫助開發,所以這時候的結果會有誤導性。
每一幀都應該在 1/60 秒(大約 16 ms)內建立並顯示。如果有一幀超時(任意影象)而無法顯示,就導致了卡頓,圖表之一就會展示出來一個紅色豎條。如果是在 UI 圖表出現了紅色豎條,則表明 Dart 程式碼消耗了大量資源。而如果紅色豎條是在 GPU 圖表出現的,意味著場景太複雜導致無法快速渲染。
紅色豎條表明當前幀的渲染和繪製都很耗時。
當兩張圖表都是紅色時,就要開始對 UI 執行緒 (Dart VM) 進行診斷了。
Flutter 的執行緒
Flutter 使用多個執行緒來完成其必要的工作,圖層中僅展示了其中兩個執行緒。您寫的所有 Dart 程式碼都在 UI 執行緒上執行。儘管您沒有直接存取其他執行緒的許可權,但是您對 UI 執行緒的操作會對其他執行緒產生效能影響。
-
平台執行緒
-
平台執行緒實際上就是主執行緒。Plugin 的程式碼將會在這裡執行。想要了解更多資訊,請參閱 Android 的 MainThread 以及 iOS 的 UIKit 文件。
-
UI 執行緒
-
UI 執行緒在 Dart VM 中執行 Dart 程式碼。該執行緒包括開發者寫下的程式碼和 Flutter 框架根據應用行為產生的程式碼。當應用建立和展示場景的時候,UI 執行緒首先建立一個 圖層樹(layer tree) ,一個包含裝置無關的渲染命令的輕量物件,並將圖層樹傳送到 GPU 執行緒來渲染到裝置上。 不要阻塞這個執行緒! 在效能圖層的最低欄展示該執行緒。
-
Raster 執行緒(以前叫 GPU 執行緒)
-
raster 執行緒拿到 layer tree,並將它交給 GPU(圖形處理單元)。你無法直接與 GPU 執行緒或其資料通訊,但如果該執行緒變慢,一定是開發者 Dart 程式碼中的某處導致的。圖形庫 Skia 在該執行緒執行,並在效能圖層的最頂欄顯示該執行緒。這個執行緒之前被叫做「GPU 執行緒」,因為它為 GPU 進行網格化,但我們重新將它命名為「raster 執行緒」,這是因為許多開發者錯誤的(但是能理解)認為該執行緒執行在 GPU 單元。
-
I/O執行緒
-
執行昂貴的操作(常見的有 I/O)以避免阻塞 UI 或者 raster 執行緒。這個執行緒將不會顯示在 performance overlay 上。
你可以在 GitHub wiki 上的框架結構 (The Framework architecture) 一文中瞭解更多資訊和一些影片內容,另外你可以在我們的社群中檢視文章 The Layer Cake。
顯示效能圖層
你可以用如下方法顯示效能圖層:
-
使用 Flutter Inspector
-
從命令列啟動
-
寫入程式碼
使用 Flutter inspector
開啟 PerformanceOverlay widget 最簡單的方法是 IDE 中 Flutter 外掛提供的 Flutter inspector,你可以在 開發者工具 的 使用 Flutter inspector 工具 中找到。只需單擊 Performance Overlay 按鈕,即可在正在執行的應用程式上切換圖層。
命令列
使用 P 引數觸發效能圖層。
程式碼控制
若要以程式設計的方式啟用效能圖層,請參考 以程式設計方式除錯應用 文件的 效能圖層 章節。
定位 UI 圖表中的問題
如果效能圖層的 UI 圖表顯示紅色,就要從分析 Dart VM 開始著手了,即使 GPU 圖表同樣顯示紅色。
定位 GPU 圖表中的問題
有些情況下介面的圖層樹構造起來雖然容易,但在 raster 執行緒下渲染卻很耗時。這種情況發生時,UI 圖表沒有紅色,但 GPU 圖表會顯示紅色。這時需要找出程式碼中導致渲染緩慢的原因。特定型別的負載對 GPU 來說會更加複雜。可能包括不必要的對 saveLayer
的呼叫,許多物件間的複雜操作,還可能是特定情形下的裁剪或者陰影。
如果推斷的原因是動畫中的卡頓的話,可以點選 Flutter inspector 中的 Slow Animations 按鈕,來使動畫速度減慢 5 倍。如果你想從更多方面控制動畫速度,你可以參考 programmatically。
卡頓是第一幀發生的還是貫穿整個動畫過程呢?如果是整個動畫過程的話,會是裁剪導致的嗎?也許有可以替代裁剪的方法來繪製場景。比如說,不透明圖層的長方形中用尖角來取代圓角裁剪。如果是一個靜態場景的淡入、旋轉或者其他操作,可以嘗試使用重繪邊界 (RepaintBoundary
)。
檢查螢幕之外的檢視
儲存圖層 (saveLayer
) 方法是 Flutter 框架中最重量的操作之一。更新螢幕時這個方法很有用,但它可能使應用變慢,如果不是必須的話,應該避免使用這個方法。即便沒有明確地呼叫 saveLayer
,也可能在其他操作中間接呼叫了該方法。可以使用棋盤畫面以外的層
(PerformanceOverlayLayer.checkerboardOffscreenLayers
)
開關來檢查場景是否使用了 saveLayer
。
開啟開關之後,執行應用並檢查是否有影象的輪廓閃爍。如果有新的幀渲染的話,容器就會閃爍。舉個例子,也許有一組物件的透明度要使用 saveLayer
來渲染。在這種情況下,相比透過 widget 樹中高層次的父 widget 操作,單獨對每個 widget 來應用透明度可能效能會更好。其他可能大量消耗資源的操作也同理,比如裁剪或者陰影。
當遇到對 saveLayer
的呼叫時,先問問自己:
-
應用是否需要這個效果?
-
可以減少呼叫麼?
-
可以對單獨元素操作而不是一組元素麼?
檢查沒有快取的影象
使用重繪邊界 (RepaintBoundary
) 來快取圖片是個好主意,當需要的時候。
從資源的角度看,最重量級的操作之一是用影象檔案來渲染紋理。首先,需要從持久儲存中取出壓縮影象,然後解壓縮到宿主儲存中(GPU 儲存),再傳輸到裝置儲存器中 (RAM) 。
也就是說,影象的 I/O 操作是重量級的。快取提供了複雜層次的快照,這樣就可以方便地渲染到隨後的幀中。 因為光柵快取入口的建構需要大量資源,同時增加了 GPU 儲存的負載,所以只在必須時才快取圖片。
開啟覆蓋層效能棋盤格光柵快取影象
(PerformanceOverlayLayer.checkerboardRasterCacheImages
) 開關可以檢查哪些圖片被快取了。
執行應用來檢視使用隨機顏色網格渲染的影象,標識被快取的影象。當和場景互動時,網格里的圖片應該是靜止的—代表重新快取圖片的閃爍檢視不應該出現。
大多數情況下,開發者都希望在網格里看到的是靜態圖片,而不是非靜態圖片。如果靜態圖片沒有被快取,可以將其放到重繪邊界
(RepaintBoundary
) widget 中來快取。雖然引擎也可能忽略 repaint boundary,如果它認為影象還不夠複雜的話。
檢視 widget 重建效能
Flutter 框架的設計使得建構達不到 60 fps 流暢度的應用變得困難。通常情況下如果卡頓,就是因為每一幀被重建的 UI 比需求更多的簡單 bug。 Widget rebuild profiler 可以幫助除錯和修復這些問題引起的 bug。
可以檢視 widget inspector 中當前螢幕和幀下的 widget 重建數量。瞭解細節,可以參考 在 Android Studio 或類 IntelliJ 裡開發 Flutter 應用 中的 顯示效能資料。
評分
可以透過編寫評分測試來測量和追蹤應用的效能。 Flutter Driver 庫提供了對評分的支援。基於這套測試框架就可以產生以下幾項的測試標準:
-
卡頓
-
下載大小
-
電池效能
-
啟動時間
追蹤這些評分可以在迴歸測試中瞭解對效能的不利影響。
瞭解更多,請參考 測試 Flutter 應用 中的 整合測試 一節。
更多資源
以下連結提供了關於 Flutter 工具的使用和 Flutter 除錯的更多資訊:
-
Flutter Inspector talk, 一個在 DartConf 2018 大會的演講
-
Hackernoon 專欄的一篇文章 為什麼 Flutter 使用 Dart
-
Flutter YouTube 頻道的一個影片:為什麼 Flutter 使用 Dart 的
-
Dart 開發者工具: Dart 和 Flutter 應用的開發者效能除錯工具;
-
Flutter API 文件, 特別是
PerformanceOverlay
這個類和 dart:developer 這個 package。