Flutter 應用性能檢測與優化

概述

軟件項目的交付是一個複雜且漫長的過程,任何細小的失誤都有可能致使交付過程失敗。在軟件開發過程當中,除了代碼邏輯的 Bug 和視覺異常這些功能層面的問題以外,移動應用另外一類常見的問題是性能問題,好比滑動操做不流暢、頁面出現卡頓丟幀現象等。這些問題雖然不至於讓移動應用徹底不可用,但也很容易引發用戶反感,從而對應用質量產生質疑,甚至失去耐心。前端

那麼,對於應用渲染並不流暢,出現了性能問題,咱們該如何檢測,又該從哪裏着手處理呢?和移動開發相似, Flutter 的性能問題主要能夠分爲 GPU 線程問題和 UI 線程(CPU)問題兩類。對於這些問題,有一個通用的套路:首先,都須要先經過性能圖層進行初步分析,而一旦確認問題存在,接下來就是利用 Flutter 提供的各種分析工具來進行問題定位。android

圖層分析

Flutter運行模式

一、Debug

Debug模式能夠在真機和模擬器上同時運行,此模式會打開全部的斷言,包括debugging信息、debugger aids(好比observatory)和服務擴展。優化了快速develop/run循環,可是沒有優化執行速度、二進制大小和部署。命令flutter run就是以這種模式運行的,經過sky/tools/gn --android或者sky/tools/gn --ios來構建應用的。ios

二、Release

Release模式只能在真機上運行,不能在模擬器上運行:會關閉全部斷言和debugging信息,關閉全部debugger工具。優化了快速啓動、快速執行和減少包體積。禁用全部的debugging aids和服務擴展。這個模式是爲了部署給最終的用戶使用。命令flutter run --release就是以這種模式運行的,經過sky/tools/gn --android --runtime-mode=release或者sky/tools/gn --ios --runtime-mode=release來構建應用。算法

三、Profile

Profile模式只能在真機上運行,不能在模擬器上運行,基本和Release模式一致,除了啓用了服務擴展和tracing,以及一些爲了最低限度支持tracing運行的東西(好比能夠鏈接observatory到進程)。命令flutter run --profile就是以這種模式運行的,經過sky/tools/gn --android --runtime-mode=profile或者sky/tools/gn --ios --runtime-mode=profile來構建應用。編程

四、test

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 產生了卡頓(跳幀),這些圖表能夠幫助咱們分析並找到緣由,以下圖所示。

在這裏插入圖片描述
上圖演示了性能圖層的展示樣式。其中,GPU 線程的性能狀況在上面,UI 線程的狀況顯示在下面,藍色垂直的線條表示已執行的正常幀,綠色的線條表明的是當前幀。

同時,爲了保持 60Hz 的刷新頻率,GPU 線程與 UI 線程中執行每一幀耗費的時間都應該小於 16ms(1/60 秒)。在這其中有一幀處理時間過長,就會致使界面卡頓,圖表中就會展現出一個紅色豎條,以下圖所示。

在這裏插入圖片描述
若是紅色豎條出如今 GPU 線程圖表,意味着渲染的圖形太複雜,致使沒法快速渲染;而若是是出如今了 UI 線程圖表,則表示 Dart 代碼消耗了大量資源,須要優化代碼執行時間。

GPU問題定位

GPU渲染問題主要集中在底層渲染耗時上,有時候 Widget 樹雖然構造起來容易,但在 GPU 線程下的渲染卻很耗時。例如,涉及 Widget 裁剪、蒙層這類多視圖疊加渲染,或是因爲缺乏緩存致使靜態圖像的反覆繪製,都會明顯拖慢 GPU 的渲染速度。

接下來,使用性能圖層提供的兩項參數,即檢查多視圖疊加的視圖渲染開關 checkerboardOffscreenLayers和檢查緩存的圖像開關checkerboardRasterCacheImages來檢查這兩種狀況。

checkerboardOffscreenLayers

多視圖疊加一般會用到 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效果以下圖所示。

在這裏插入圖片描述
當咱們開啓checkerboardOffscreenLayers以後,能夠看到視圖蒙層效果對GPU的渲染壓力致使性能視圖頻繁閃動。若是咱們沒有對動態模糊效果有特殊需求,則可使用不帶模糊效果的 Scaffold 和白色的 AppBar 實現一樣的產品功能,來解決這個性能問題。

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 檢測圖層也再也不頻繁閃爍了。

在這裏插入圖片描述

checkerboardRasterCacheImages

從資源的角度看,另外一類很是消耗性能的操做是渲染圖像,由於圖像渲染會涉及 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,
    ),
));
複製代碼

UI 線程問題定位

若是說 GPU 線程問題定位的是渲染引擎底層渲染異常,那麼 UI 線程問題發現的則是應用的性能瓶頸。好比在視圖構建時,在 build 方法中使用了一些複雜的運算,或是在主 Isolate 中進行了同步的 I/O 操做。這些問題,都會明顯增長 CPU 的處理時間,拖慢應用的響應速度。

針對這類問題,咱們可使用 Flutter 提供的 Performance 工具,來記錄應用的執行軌跡。Performance 是一個強大的性能分析工具,可以以時間軸的方式展現 CPU 的調用棧和執行時間,去檢查代碼中可疑的方法調用。

打開 Android Studio 底部工具欄中的「Open DevTools」按鈕以後,系統會自動打開 Dart DevTools 的網頁,將頂部的 tab 切換到 Performance 後,咱們就能夠開始分析代碼中的性能問題了。

在這裏插入圖片描述
在這裏插入圖片描述
接下來,咱們經過一個在 ListView 中計算 MD5 的例子來演示 Performance 的具體分析過程。考慮到在 build 函數中進行渲染信息的組裝是一個常見的操做,爲了演示Performance的使用過程,咱們故意放大計算 MD5 的耗時,如循環迭代計算了 1 萬次。

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 耗時問題,皆能夠查看火焰圖底部的哪一個函數佔據的寬度最大。只要有「平頂」,就表示該函數可能存在性能問題,以下圖所示。

在這裏插入圖片描述
能夠看到,_MyHomePage.generateMd5 函數的執行時間最長,幾乎佔滿了整個火焰圖的寬,而這也與代碼中存在的問題是一致的。在找到了問題以後,咱們就可使用 Isolate(或 compute)將這些耗時的操做挪到併發主 Isolate 以外去完成了。

總結

在 Flutter 中,性能分析過程能夠分爲 GPU 線程問題定位和 UI 線程(CPU)問題定位,而它們都須要在真機上以分析模式(Profile)啓動應用,並經過性能圖層分析大體的渲染問題範圍。 一旦確認問題存在,接下來就須要利用 Flutter 所提供的分析工具來定位問題緣由了。關於 GPU 線程渲染問題,咱們能夠重點檢查應用中是否存在多視圖疊加渲染,或是靜態圖像反覆刷新的現象。而 UI 線程渲染問題,咱們則是經過 Performance 工具記錄的火焰圖(CPU 幀圖),分析代碼耗時來找出應用執行瓶頸。

總的來講,因爲 Flutter 採用基於聲明式的 UI 設計理念,以數據驅動渲染,並採用 Widget->Element->RenderObject 三層結構,屏蔽了無謂的界面刷新,可以保證絕大多數狀況下咱們構建的應用都是高性能的,因此在使用分析工具檢測出性能問題以後,一般咱們並不須要作太多的細節優化工做,只須要在改造過程當中避開一些常見的坑,就能夠得到優異的性能。同時,爲了不形成性能問題,還應該從如下幾個方面着手:

  • 控制 build 方法耗時,將 Widget 拆小,避免直接返回一個巨大的 Widget,這樣 Widget 會享有更細粒度的重建和複用;
  • 儘可能不要爲 Widget 設置半透明效果,而是考慮用圖片的形式代替,這樣被遮擋的 Widget 部分區域就不須要繪製了;
  • 對列表採用懶加載而不是直接一次性建立全部的子 Widget,這樣視圖的初始化時間就減小了。

參考資料

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,之後會封殺嗎

相關文章
相關標籤/搜索