軟件項目的交付是一個複雜且漫長的過程,任何細小的失誤都有可能致使交付過程失敗。在軟件開發過程當中,除了代碼邏輯的 Bug 和視覺異常這些功能層面的問題以外,移動應用另外一類常見的問題是性能問題,好比滑動操做不流暢、頁面出現卡頓丟幀現象等。這些問題雖然不至於讓移動應用徹底不可用,但也很容易引發用戶反感,從而對應用質量產生質疑,甚至失去耐心。前端
那麼,對於應用渲染並不流暢,出現了性能問題,咱們該如何檢測,又該從哪裏着手處理呢?和移動開發相似, Flutter 的性能問題主要能夠分爲 GPU 線程問題和 UI 線程(CPU)問題兩類。對於這些問題,有一個通用的套路:首先,都須要先經過性能圖層進行初步分析,而一旦確認問題存在,接下來就是利用 Flutter 提供的各種分析工具來進行問題定位。android
Debug模式能夠在真機和模擬器上同時運行,此模式會打開全部的斷言,包括debugging信息、debugger aids(好比observatory)和服務擴展。優化了快速develop/run循環,可是沒有優化執行速度、二進制大小和部署。命令flutter run就是以這種模式運行的,經過sky/tools/gn --android
或者sky/tools/gn --ios
來構建應用的。ios
Release模式只能在真機上運行,不能在模擬器上運行:會關閉全部斷言和debugging信息,關閉全部debugger工具。優化了快速啓動、快速執行和減少包體積。禁用全部的debugging aids和服務擴展。這個模式是爲了部署給最終的用戶使用。命令flutter run --release
就是以這種模式運行的,經過sky/tools/gn --android --runtime-mode=release
或者sky/tools/gn --ios --runtime-mode=release
來構建應用。算法
Profile模式只能在真機上運行,不能在模擬器上運行,基本和Release模式一致,除了啓用了服務擴展和tracing,以及一些爲了最低限度支持tracing運行的東西(好比能夠鏈接observatory到進程)。命令flutter run --profile
就是以這種模式運行的,經過sky/tools/gn --android --runtime-mode=profile
或者sky/tools/gn --ios --runtime-mode=profile
來構建應用。編程
headless test模式只能在桌面上運行,基本和Debug模式一致,除了是headless的並且你能在桌面運行。命令flutter test
就是以這種模式運行的,經過sky/tools/gn
來build。緩存
在實際開發中,應該用到上面所說的四種模式又各自分爲兩種:一種是未優化的模式,供開發人員調試使用;一種是優化過的模式,供最終的開發人員使用。默認狀況下是未優化模式,若是要開啓優化模式,build的時候在命令行後面添加--unoptimized參數。bash
無論是移動開發仍是前端開發,對於性能問題分析的思路都是先分析並定位問題,Flutter也不例外,藉助Flutter 提供的度量性能工具,咱們能夠快速定位代碼中的性能問題,而性能圖層就是幫助咱們確認問題影響範圍的利器,它相似Android的圖層分析工具。網絡
爲了使用性能圖層,Flutter提供了分析(Profile)模式,與調試代碼能夠經過模擬器在調試模式下找到代碼邏輯 Bug 不一樣,性能問題須要在發佈模式下使用真機進行檢測。相比發佈(Release)模式而言,調試模式增長了不少額外的檢查(好比斷言),這些檢查可能會耗費不少資源;更重要的是,調試模式使用 JIT (即時編譯)模式運行應用,代碼執行效率較低。這就使得調試模式運行的應用,沒法真實反映出它的性能問題。併發
而另外一方面,模擬器使用的指令集爲 x86,而真機使用的指令集是 ARM,因爲這兩種方式的二進制代碼執行行爲徹底不一樣,所以模擬器與真機的性能差別較大。一些 x86 指令集擅長的操做模擬器會比真機快,而另外一些操做則會比真機慢,這也使得咱們沒法使用模擬器來評估真機才能出現的性能問題。app
爲了調試性能問題,咱們須要在發佈模式的基礎之上,爲分析工具提供少許必要的應用追蹤信息,這就是分析模式。除了一些調試性能問題必須的追蹤方法以外,Flutter 應用的分析模式和發佈模式的編譯和運行是相似的,只是啓動參數變成了 profile 而已。咱們能夠在 Android Studio 中經過菜單欄點擊 【Run】-【Profile 】‘main.dart’ 選項啓動應用,也能夠經過命令行參數 flutter run --profile
運行 Flutter 應用。
在完成了應用啓動以後,接下來咱們就能夠利用 Flutter 提供的渲染問題分析工具,即性能圖層(Performance Overlay)來分析渲染問題了。性能圖層會在當前應用的最上層,以 Flutter 引擎自繪的方式展現 GPU 與 UI 線程的執行圖表,而其中每一張圖表都表明當前線程最近 300 幀的表現,若是 UI 產生了卡頓(跳幀),這些圖表能夠幫助咱們分析並找到緣由,以下圖所示。
同時,爲了保持 60Hz 的刷新頻率,GPU 線程與 UI 線程中執行每一幀耗費的時間都應該小於 16ms(1/60 秒)。在這其中有一幀處理時間過長,就會致使界面卡頓,圖表中就會展現出一個紅色豎條,以下圖所示。
GPU渲染問題主要集中在底層渲染耗時上,有時候 Widget 樹雖然構造起來容易,但在 GPU 線程下的渲染卻很耗時。例如,涉及 Widget 裁剪、蒙層這類多視圖疊加渲染,或是因爲缺乏緩存致使靜態圖像的反覆繪製,都會明顯拖慢 GPU 的渲染速度。
接下來,使用性能圖層提供的兩項參數,即檢查多視圖疊加的視圖渲染開關 checkerboardOffscreenLayers和檢查緩存的圖像開關checkerboardRasterCacheImages來檢查這兩種狀況。
多視圖疊加一般會用到 Canvas 裏的 savaLayer 方法,這個方法在實現一些特定的效果(好比半透明)時很是有用,但因爲其底層實現會在 GPU 渲染上涉及多圖層的反覆繪製,所以會帶來較大的性能問題。
對於 saveLayer 方法使用狀況的檢查,咱們只須要在 MaterialApp 的初始化方法中,將 checkerboardOffscreenLayers 開關設置爲 true,分析工具就會自動幫咱們檢測多視圖疊加的狀況。使用了 saveLayer 的 Widget 會自動顯示爲棋盤格式,並隨着頁面刷新而閃爍。不過,saveLayer 是一個較爲底層的繪製方法,所以咱們通常不會直接使用它,而是會經過一些功能性 Widget,在涉及須要剪切或半透明蒙層的場景中間接地使用。因此一旦遇到這種狀況,咱們須要思考一下是否必定要這麼作,能不能經過其餘方式來實現呢?
好比下面的例子中,咱們使用 CupertinoPageScaffold 與 CupertinoNavigationBar 實現了一個動態模糊的效果,代碼以下:
CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),//動態模糊導航欄
child: ListView.builder(
itemCount: 100,
//爲列表建立100個不一樣顏色的RowItem
itemBuilder: (context, index)=>TabRowItem(
index: index,
lastItem: index == 100 - 1,
color: colorItems[index],//設置不一樣的顏色
colorName: colorNameItems[index],
)
)
);
複製代碼
其中,動態模糊的NavigationBar效果以下圖所示。
Scaffold(
//使用普通的白色AppBar
appBar: AppBar(title: Text('Home', style: TextStyle(color:Colors.black),),backgroundColor: Colors.white),
body: ListView.builder(
itemCount: 100,
//爲列表建立100個不一樣顏色的RowItem
itemBuilder: (context, index)=>TabRowItem(
index: index,
lastItem: index == 100 - 1,
color: colorItems[index],//設置不一樣的顏色
colorName: colorNameItems[index],
)
),
);
複製代碼
運行一下代碼,能夠看到,在去掉了動態模糊效果以後,GPU 的渲染壓力獲得了緩解,checkerboardOffscreenLayers 檢測圖層也再也不頻繁閃爍了。
從資源的角度看,另外一類很是消耗性能的操做是渲染圖像,由於圖像渲染會涉及 I/O、GPU 存儲以及不一樣通道的數據格式轉換,所以渲染過程的構建須要消耗大量資源。爲了緩解 GPU 的壓力,Flutter 提供了多層次的緩存快照,這樣 Widget 重建時就無需從新繪製靜態圖像了。
與檢查多視圖疊加渲染的 checkerboardOffscreenLayers 參數相似,Flutter 提供了檢查緩存圖像的開關 checkerboardRasterCacheImages,來檢測在界面重繪時頻繁閃爍的圖像。
爲了提升靜態圖像顯示性能,咱們能夠把須要靜態緩存的圖像加到 RepaintBoundary 中,RepaintBoundary 能夠肯定 Widget 樹的重繪邊界,若是圖像足夠複雜,Flutter 引擎會自動將其緩存,從而避免重複刷新。固然,由於緩存資源有限,若是引擎認爲圖像不夠複雜,也可能會忽略 RepaintBoundary。下面的代碼展現了經過 RepaintBoundary,將一個靜態複合 Widget 加入緩存的具體用法,以下所示。
RepaintBoundary(//設置靜態緩存圖像
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
));
複製代碼
若是說 GPU 線程問題定位的是渲染引擎底層渲染異常,那麼 UI 線程問題發現的則是應用的性能瓶頸。好比在視圖構建時,在 build 方法中使用了一些複雜的運算,或是在主 Isolate 中進行了同步的 I/O 操做。這些問題,都會明顯增長 CPU 的處理時間,拖慢應用的響應速度。
針對這類問題,咱們可使用 Flutter 提供的 Performance 工具,來記錄應用的執行軌跡。Performance 是一個強大的性能分析工具,可以以時間軸的方式展現 CPU 的調用棧和執行時間,去檢查代碼中可疑的方法調用。
打開 Android Studio 底部工具欄中的「Open DevTools」按鈕以後,系統會自動打開 Dart DevTools 的網頁,將頂部的 tab 切換到 Performance 後,咱們就能夠開始分析代碼中的性能問題了。
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
String generateMd5(String data) {
//MD5固定算法
var content = new Utf8Encoder().convert(data);
var digest = md5.convert(content);
return hex.encode(digest.bytes);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('demo')),
body: ListView.builder(
itemCount: 30,// 列表元素個數
itemBuilder: (context, index) {
//反覆迭代計算MD5
String str = '1234567890abcdefghijklmnopqrstuvwxyz';
for(int i = 0;i<10000;i++) {
str = generateMd5(str);
}
return ListTile(title: Text("Index : $index"), subtitle: Text(str));
}// 列表項建立方法
),
);
}
}
複製代碼
與性能圖層可以自動記錄應用執行狀況不一樣,使用 Performance 來分析代碼執行軌跡,咱們須要手動點擊【Record】按鈕去主動觸發,在完成信息的抽樣採集後再點擊【Stop】按鈕結束錄製,而後就能夠獲得在這期間應用的執行狀況了。
Performance 記錄的應用執行狀況叫作 CPU 幀圖,又被稱爲火焰圖。火焰圖是基於記錄代碼執行結果所產生的圖片,用來展現 CPU 的調用棧,表示的是 CPU 的繁忙程度。因此,咱們要檢測 CPU 耗時問題,皆能夠查看火焰圖底部的哪一個函數佔據的寬度最大。只要有「平頂」,就表示該函數可能存在性能問題,以下圖所示。
在 Flutter 中,性能分析過程能夠分爲 GPU 線程問題定位和 UI 線程(CPU)問題定位,而它們都須要在真機上以分析模式(Profile)啓動應用,並經過性能圖層分析大體的渲染問題範圍。 一旦確認問題存在,接下來就須要利用 Flutter 所提供的分析工具來定位問題緣由了。關於 GPU 線程渲染問題,咱們能夠重點檢查應用中是否存在多視圖疊加渲染,或是靜態圖像反覆刷新的現象。而 UI 線程渲染問題,咱們則是經過 Performance 工具記錄的火焰圖(CPU 幀圖),分析代碼耗時來找出應用執行瓶頸。
總的來講,因爲 Flutter 採用基於聲明式的 UI 設計理念,以數據驅動渲染,並採用 Widget->Element->RenderObject 三層結構,屏蔽了無謂的界面刷新,可以保證絕大多數狀況下咱們構建的應用都是高性能的,因此在使用分析工具檢測出性能問題以後,一般咱們並不須要作太多的細節優化工做,只須要在改造過程當中避開一些常見的坑,就能夠得到優異的性能。同時,爲了不形成性能問題,還應該從如下幾個方面着手:
參考資料
1,Flutter 應用程序調試
2,Flutter For Web入門實戰
3,Flutter開發之路由與導航
4,Flutter 必備開源項目
5,Flutter混合開發
6,Flutter的Hot Reload是如何作到的
7,《Flutter in action》開源
8,Flutter開發之JSON解析
9,Flutter開發之基礎Widgets
10,Flutter開發之導航與路由管理
11,Flutter開發之網絡請求
12,Flutter基礎知識
13,Flutter開發之Dart語言基礎
14,Flutter入門與環境搭建
15,移動跨平臺方案對比:WEEX、React Native、Flutter和PWA
16,Flutter開發之異步編程
17,構建屬於本身的Flutter混合開發框架
18,Flutter應用集成極光推送
19,Flutter 國際化適配實戰
20,Apple爲何不封殺 Flutter,之後會封殺嗎