App 流暢性的關鍵指標有 UI幀率,GPU幀率,咱們指望它能達到 60fps,也就是16ms每幀。html
爲了獲取最接近生產環境的數據,咱們應該選擇一臺儘量低端的真機,而且以 profile 模式或者 release 模式下運行app。android
- 由於 debug 模式會有一些額外的檢查工做,好比
assert()
等- 爲了加速開發效率,debug 模式是以 JIT(Just in time)模式編譯 dart 代碼的,而 profile 和 release 是提早編譯爲機器碼 AOT(Ahead Of Time),因此 debug 會慢不少
在 Android Studio and IntelliJ 中, 在菜單欄中點擊 Run > Flutter Run main.dart in Profile Mode
git
VS Code:打開 launch.json 文件並設置flutterMode 爲 profile:github
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "profile" # 測試完後記得把它改回去!
}
]
複製代碼
$ flutter run --profile
複製代碼
那麼檢測幀率有哪些方法呢?Flutter 給咱們提供了 Performance Overlay
,以下圖,綠色表明當前渲染幀。算法
咱們有三種開啓方式json
View > Tool Windows > Flutter Inspector
. 點擊下面這個按鈕。在 VS Code中 選中 View > Command Palette…
會顯示一個 command 面板. 在命令面板中輸入 performance
並選擇 Toggle Performance Overlay
若是命令顯示爲不可用,須要檢查 app 是否正在運行.redux
從命令行中運行 鍵盤輸入P
瀏覽器
代碼中打開 在MaterialApp
或者 WidgetsApp
的構造函數中設置showPerformanceOverlay
屬性爲 true
:緩存
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true, // 開啓
title: 'My Awesome App',
home: MyHomePage(title: 'My Awesome App'),
);
}
}
複製代碼
而後就是動手操做 app,並觀察圖表上是否出現紅色線條。綠色表明當前幀,當頁面有變更,圖表會不斷繪製。蒙版上有2個圖表,每一個圖表上有三橫格,每一個橫格表明16ms。若是大多數幀都在第一格,說明達到了指望的幀率。性能優化
圖表分別體現了 UI幀率 和 GPU幀率。若是出現了紅色,說明對應的線程有太多work要作。那先來了解一下 Flutter 中的4個主要線程分別承擔了什麼職責。
在運行app的過程當中,觀察爆紅的地方和觸發場景,進行分析。
saveLayer
操做保存爲一個圖層,最後給這個圖層設置透明度。而saveLayer
開銷很大,這裏官方給出了一個建議:首先確認這些效果是否真的有必要;若是有必要,咱們能夠把透明度設置到每一個子控件上,而不是父控件。裁剪操做也是相似。RepaintBoundry
控件中,引擎會自動判斷圖像是否複雜到須要用repaint boundary,不須要的話也會忽略。MaterialApp(
showPerformanceOverlay: true,
checkerboardOffscreenLayers: true, // 使用了saveLayer的圖形會顯示爲棋盤格式並隨着頁面刷新而閃爍
checkerboardRasterCacheImages: true, // 作了緩存的靜態圖片在刷新頁面時不會改變棋盤格的顏色;若是棋盤格顏色變了說明被從新緩存了,這是咱們要避免的
...
);
複製代碼
在內存優化方面,咱們的目標是但願減小應用內存佔用,減小被系統殺死的機率,同時儘量的避免內存泄露,減小內存碎片化。
Dart 提供了一個性能檢測工具Observatory,我在最後一部分會進行詳細介紹
性能優化不像其它的開發需求只要完成功能便可,它須要經過統計和數據來證實優化的效果。好比幀率有了多少提升?CPU佔用率下降了多少?內存佔用減小了多少?對比其它優化策略,哪一個優化效果好?
以檢查流暢性爲例
scroll_pref.dart
void main() {
enableFlutterDriverExtension();
runApp(const GalleryApp(testMode: true));
}
複製代碼
scroll_perf_test.dart
void main() {
group('scrolling performance test', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null)
driver.close();
});
test('measure', () async {
final Timeline timeline = await driver.traceAction(() async {
await driver.tap(find.text('Material'));
final SerializableFinder demoList = find.byValueKey('GalleryDemoList');
for (int i = 0; i < 5; i++) {
await driver.scroll(demoList, 0.0, -300.0, const Duration(milliseconds: 300));
await Future<void>.delayed(const Duration(milliseconds: 500));
}
// Scroll up
for (int i = 0; i < 5; i++) {
await driver.scroll(demoList, 0.0, 300.0, const Duration(milliseconds: 300));
await Future<void>.delayed(const Duration(milliseconds: 500));
}
});
TimelineSummary.summarize(timeline)
..writeSummaryToFile('home_scroll_perf', pretty: true)
..writeTimelineToFile('home_scroll_perf', pretty: true);
});
});
}
複製代碼
在命令行下執行如下命令
flutter driver --target=test_driver/scroll_perf.dart
複製代碼
這個命令會:
test_driver/
目錄下的scroll_perf_test.dart
的測試( flutter drive 能幫你找到帶 _test
後綴的同名文件)Test Driver 將會安裝 app 到設備上,再跳轉到 Material-GalleryDemoList 頁面,作5次滑動列表的操做。執行完成後會藉助 TimelineSummary
,在build目錄下生成兩個json文件:home_scroll_perf.timeline.json
和home_scroll_perf.timeline_summary.json
。這裏咱們看一下timeline_summary.json
文件的內容
{
"average_frame_build_time_millis": 5.6319655172413805, # 平均每幀 build 時間
"90th_percentile_frame_build_time_millis": 10.216,
"99th_percentile_frame_build_time_millis": 17.168,
"worst_frame_build_time_millis": 20.415, # 最長幀 build 時間
"missed_frame_build_budget_count": 21, # build 期丟幀數
"average_frame_rasterizer_time_millis": 14.234294964028772, # 平均每幀光柵化時間
"90th_percentile_frame_rasterizer_time_millis": 22.338,
"99th_percentile_frame_rasterizer_time_millis": 42.661,
"worst_frame_rasterizer_time_millis": 43.161,
"missed_frame_rasterizer_budget_count": 112,
"frame_count": 116,
"frame_build_times": [
...
],# 全部幀的 build 時間
"frame_rasterizer_times": [
...
] # 全部幀的光柵化時間
}
複製代碼
更多能夠參考官方文檔
Observatory 是用於分析和調試Dart應用程序的工具。Observatory容許您根據須要查看正在運行的Dart虛擬機(VM),並提供實時,即時的數據報告。您可使用它來瀏覽應用程序的不少狀態。
有2種方式:
Flutter Inspector
面板,點擊小鬧鐘圖標,以下圖
flutter run
,應用啓動成功後,命令行中會輸出一個 url,把 url copy 到瀏覽器便可。
打開Observatory面板,要先選擇isolate,表示當前應用。
下面是性能優化常關注的幾個頁面。
app的時間都花在哪了?
進入這個頁面後要通常需加載個幾秒鐘,so be patient。圖表的下部按cpu佔用比例作了一個列表,反映的是函數的調用次數和執行時間(劃重點)。通常排在前面的函數(這些函數是?有待學習)都不是咱們寫的dart代碼。若是你發現本身的某個函數調用佔比反常,那麼可能存在問題。
注:flutter程序的cpu profile和官方文檔上的數據展現不太同樣,沒有VM tags,因此對於百分比的具體含義有待研究。
採樣過程:它每隔必定時間對isolate作採樣,採樣的數據存儲在一個環形緩衝區(叫作profile),它能存放約2分鐘的數據,一旦緩衝區滿了,它會用最新的sample替換掉最舊的。
內存都被誰吃了?
Heap 堆,動態分配的Dart對象所在的內存空間
經過這個面板你能看到新生代/老生代的內存大小和佔比;每一個類型所佔用的內存大小。
爲了debug的方便,咱們能夠獲取到某段時間的內存分配狀況:點擊Reset Accumulator按鈕,把數據清零,執行一下要測試的程序,點擊刷新。
爲了檢查內存泄露,咱們能夠點擊GC按鈕,手動執行GC。
Accumulator Size:自點擊Reset Accumulator以來,累加對象佔用內存大小 Accumulator Instances:自點擊Reset Accumulator以來,累加實例個數 Current Size:當前對象佔用內存大小 Current Instances:當前對象數量
是否出現內存碎片化
heap map 面板能查看old generation中的內存狀態
它以顏色顯示內存塊。 每一個內存頁面(page of memory)爲256 KB,每頁由水平黑線分隔。 像素的顏色表示對象的類ID - 例如,藍色表示字符串,綠色表示雙精度表。 可用空間爲白色,指令(代碼)爲紫色。 若是啓動垃圾收集(使用「分配配置文件」屏幕中的GC按鈕),堆映射中將顯示更多空白區域(可用空間)。 將光標懸停在上面時,頂部的狀態欄顯示有關光標下像素所表明的對象的信息。 顯示的信息包括該對象的類型,大小和地址。 當你看到白色區域中有不少分散的其它顏色,說明存在內存碎片化,多是內存泄露致使的。
知道哪些代碼執行了,哪些沒有執行
應用場景:寫某個類的單元測試,跑完測試後,能夠查看哪些代碼沒有覆蓋到,進而補全
查看某個實例的狀態,好比咱們的項目中使用了Flutter_redux,頁面的展現來源與狀態樹,當頁面出現了非預期的效果,咱們能夠經過Observatory查看狀態樹
性能優化涉及了應用的方法面面,很難一言以蔽之。本文咱們主要討論了性能優化的兩大主題 —— 流暢性和內存優化,並分別介紹了他們的檢測方法和優化策略。另外,咱們在優化的同時也要增強優化的證實,用數聽說話。最後,我強烈推薦你們嘗試一下 Observatory 這個工具,開發中若是遇到了奇怪的問題,沒準它能幫你找到答案。
本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@akindone