Flutter 性能優化 Tips

本文目的

  • 介紹應用流暢性的檢測和優化策略
  • 介紹內存的檢測和優化策略
  • 介紹性能優化證實的意義和流程
  • 介紹性能檢測工具 Observatory 的基礎使用

目錄結構

  • 流暢性
  • 內存優化
  • 優化證實
  • 性能檢測利器 Observatory 基礎使用
  • 總結

流暢性

App 流暢性的關鍵指標有 UI幀率,GPU幀率,咱們指望它能達到 60fps,也就是16ms每幀。html

以 profile / release 模式運行

爲了獲取最接近生產環境的數據,咱們應該選擇一臺儘量低端的真機,而且以 profile 模式或者 release 模式下運行app。android

  1. 由於 debug 模式會有一些額外的檢查工做,好比assert()
  2. 爲了加速開發效率,debug 模式是以 JIT(Just in time)模式編譯 dart 代碼的,而 profile 和 release 是提早編譯爲機器碼 AOT(Ahead Of Time),因此 debug 會慢不少
  1. 在 Android Studio and IntelliJ 中, 在菜單欄中點擊 Run > Flutter Run main.dart in Profile Modegit

  2. VS Code:打開 launch.json 文件並設置flutterMode 爲 profile:github

"configurations": [
	{
		"name": "Flutter",
		"request": "launch",
		"type": "dart",
		"flutterMode": "profile" # 測試完後記得把它改回去!
	}
]
複製代碼
  1. 用命令行啓動:
$ flutter run --profile
複製代碼

檢測幀率

那麼檢測幀率有哪些方法呢?Flutter 給咱們提供了 Performance Overlay,以下圖,綠色表明當前渲染幀。算法

performance-overlay-green

咱們有三種開啓方式json

  1. 在Android Studio 和 IntelliJ IDEA中: 選中 View > Tool Windows > Flutter Inspector. 點擊下面這個按鈕。

  1. 在 VS Code中 選中 View > Command Palette… 會顯示一個 command 面板. 在命令面板中輸入 performance 並選擇 Toggle Performance Overlay 若是命令顯示爲不可用,須要檢查 app 是否正在運行.redux

  2. 從命令行中運行 鍵盤輸入P瀏覽器

  3. 代碼中打開 在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。若是大多數幀都在第一格,說明達到了指望的幀率。性能優化

performance overlay jank

圖表分別體現了 UI幀率 和 GPU幀率。若是出現了紅色,說明對應的線程有太多work要作。那先來了解一下 Flutter 中的4個主要線程分別承擔了什麼職責。

  • Platform線程:插件代碼運行的線程;即Android/iOS的主線程,
  • UI線程:在Dart虛擬機中執行Dart代碼。做用是建立視圖樹,而後將它發送給GPU。注意不要阻塞此線程!
  • GPU線程:把上面提到的視圖樹渲染出來,雖然咱們在flutter中不能直接訪問GPU線程和數據,可是Dart代碼可能致使此線程變慢
  • I/O線程:執行比較耗時的任務

在運行app的過程當中,觀察爆紅的地方和觸發場景,進行分析。

分析思路

  • 若是是UI報紅:那麼多是執行了某個較耗時的函數?或者函數調用過多?算法複雜度高?
  • 若是隻是 GPU 報紅:那麼多是要繪製的圖形過於複雜?或者執行了過多GPU操做?
    • 好比要實現一個混合圖層的半透明效果:若是把透明度設置在頂層控件上,CPU會把每一個子控件圖層渲染出來,再執行saveLayer操做保存爲一個圖層,最後給這個圖層設置透明度。而saveLayer開銷很大,這裏官方給出了一個建議:首先確認這些效果是否真的有必要;若是有必要,咱們能夠把透明度設置到每一個子控件上,而不是父控件。裁剪操做也是相似。
    • 還有一個拖慢GPU渲染速度的是沒有給靜態圖像作緩存,致使每次build都會從新繪製。咱們能夠把靜態圖形加到RepaintBoundry控件中,引擎會自動判斷圖像是否複雜到須要用repaint boundary,不須要的話也會忽略。
    • 開啓saveLayer和圖形緩存的檢查
    MaterialApp(
        showPerformanceOverlay: true,
        checkerboardOffscreenLayers: true, // 使用了saveLayer的圖形會顯示爲棋盤格式並隨着頁面刷新而閃爍
        checkerboardRasterCacheImages: true, // 作了緩存的靜態圖片在刷新頁面時不會改變棋盤格的顏色;若是棋盤格顏色變了說明被從新緩存了,這是咱們要避免的
        ...
    );
    複製代碼

提升流暢性的策略

  • 代碼調用時機是否能夠延後?如底部導航欄式的頁面,沒有必要第一次進入就把每一個子Page都建立出來
  • 儘可能作到局部刷新
  • 把耗時的計算放到獨立的isolate去執行
  • 檢查沒必要要的 saveLayer
  • 檢查靜態圖片是否添加緩存
  • relayout boundary:參考
  • repaint boundary:參考

內存優化

在內存優化方面,咱們的目標是但願減小應用內存佔用,減小被系統殺死的機率,同時儘量的避免內存泄露,減小內存碎片化。

內存優化策略

  • 加載對象過大?如圖片質量和尺寸不作限制就加載
  • 加載對象過多?如加載長列表;在調用頻率很高的方法中建立對象
    • 合理設置緩存大小/長度
    • 在內存不足時或離開頁面時清空緩存數據
    • 使用ListView.build()來複用子控件
    • 自定義繪圖中避免在onDraw中作建立對象操做,或者相同的參數設置
    • 複用系統提供的資源,好比字符串、圖片、動畫、樣式、顏色、簡單佈局,在應用中直接引用
  • 內存泄露的問題?好比dispose須要銷燬的listener等
  • 不可見的視圖是否也在build?
  • 頁面離開後的網絡請求是否取消?

如何獲取內存狀態

Dart 提供了一個性能檢測工具Observatory,我在最後一部分會進行詳細介紹

優化證實

優化證實的意義

性能優化不像其它的開發需求只要完成功能便可,它須要經過統計和數據來證實優化的效果。好比幀率有了多少提升?CPU佔用率下降了多少?內存佔用減小了多少?對比其它優化策略,哪一個優化效果好?

優化證實的流程

profile prove

舉個例子

以檢查流暢性爲例

1. 在profile模式下運行並開啓Performance Overlay,總體測試app

2. 找到幀率報紅色的模塊

3. 把頁面孤立出來,並屢次測量,並獲得baseline(參照)幀率數據。好比長列表頁面出現了卡頓,咱們能夠用TestDriver寫一個ListView滑動的性能測試(更多參考Flutter gallery)

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 
複製代碼

這個命令會:

  • build 目標 app,並把它安裝到設備上
  • 運行位於test_driver/目錄下的scroll_perf_test.dart的測試( flutter drive 能幫你找到帶 _test後綴的同名文件)

Test Driver 將會安裝 app 到設備上,再跳轉到 Material-GalleryDemoList 頁面,作5次滑動列表的操做。執行完成後會藉助 TimelineSummary ,在build目錄下生成兩個json文件:home_scroll_perf.timeline.jsonhome_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": [
      ...
  ] # 全部幀的光柵化時間
}
複製代碼

4. 優化

5. 用步驟3的方法再次測量,對比baseline得出確切的優化效果

Flutter 提供的性能調試 API

更多能夠參考官方文檔

性能檢測利器 Observatory

Observatory 是用於分析和調試Dart應用程序的工具。Observatory容許您根據須要查看正在運行的Dart虛擬機(VM),並提供實時,即時的數據報告。您可使用它來瀏覽應用程序的不少狀態。

打開Observatory

有2種方式:

  1. 在 androidStudio 中打開Flutter Inspector面板,點擊小鬧鐘圖標,以下圖
    open observatory from AS
  2. 再命令行中運行flutter run,應用啓動成功後,命令行中會輸出一個 url,把 url copy 到瀏覽器便可。
    open observatory from command line

打開Observatory面板,要先選擇isolate,表示當前應用。

entey screen

主要頁面

下面是性能優化常關注的幾個頁面。

main screen

1. CPU Profile

app的時間都花在哪了?

進入這個頁面後要通常需加載個幾秒鐘,so be patient。圖表的下部按cpu佔用比例作了一個列表,反映的是函數的調用次數和執行時間(劃重點)。通常排在前面的函數(這些函數是?有待學習)都不是咱們寫的dart代碼。若是你發現本身的某個函數調用佔比反常,那麼可能存在問題。

注:flutter程序的cpu profile和官方文檔上的數據展現不太同樣,沒有VM tags,因此對於百分比的具體含義有待研究。

cpu profile

採樣過程:它每隔必定時間對isolate作採樣,採樣的數據存儲在一個環形緩衝區(叫作profile),它能存放約2分鐘的數據,一旦緩衝區滿了,它會用最新的sample替換掉最舊的。

  • Profile contains:採樣時長和對應的採樣數
  • Sampling:採樣頻率,默認1000Hz,即每毫秒採樣一次

2. Allocation Profile

內存都被誰吃了?

allocation profile

Heap 堆,動態分配的Dart對象所在的內存空間

  • New generation: 新建立的對象,通常來講對象比較小,生命週期短,如local 變量。在這裏GC活動頻繁
  • Old generation:從GC中存活下來的New generation將會提拔到老生代Old generation,它比新生代空間大,更適合大的對象和生命週期長的對象

經過這個面板你能看到新生代/老生代的內存大小和佔比;每一個類型所佔用的內存大小。

爲了debug的方便,咱們能夠獲取到某段時間的內存分配狀況:點擊Reset Accumulator按鈕,把數據清零,執行一下要測試的程序,點擊刷新。

爲了檢查內存泄露,咱們能夠點擊GC按鈕,手動執行GC。

Accumulator Size:自點擊Reset Accumulator以來,累加對象佔用內存大小 Accumulator Instances:自點擊Reset Accumulator以來,累加實例個數 Current Size:當前對象佔用內存大小 Current Instances:當前對象數量

3. Heap Map

是否出現內存碎片化

heap map 面板能查看old generation中的內存狀態

它以顏色顯示內存塊。 每一個內存頁面(page of memory)爲256 KB,每頁由水平黑線分隔。 像素的顏色表示對象的類ID - 例如,藍色表示字符串,綠色表示雙精度表。 可用空間爲白色,指令(代碼)爲紫色。 若是啓動垃圾收集(使用「分配配置文件」屏幕中的GC按鈕),堆映射中將顯示更多空白區域(可用空間)。 將光標懸停在上面時,頂部的狀態欄顯示有關光標下像素所表明的對象的信息。 顯示的信息包括該對象的類型,大小和地址。 當你看到白色區域中有不少分散的其它顏色,說明存在內存碎片化,多是內存泄露致使的。

其它

1. Code Coverage

知道哪些代碼執行了,哪些沒有執行

code coverage

  • 綠色:已執行的代碼
  • 紅色:未執行的代碼
  • 沒有顏色:不可執行的代碼

應用場景:寫某個類的單元測試,跑完測試後,能夠查看哪些代碼沒有覆蓋到,進而補全

2. Class/Instance 信息

查看某個實例的狀態,好比咱們的項目中使用了Flutter_redux,頁面的展現來源與狀態樹,當頁面出現了非預期的效果,咱們能夠經過Observatory查看狀態樹

watch state

舉個例子

Observatory 幫我找到循環調用的真兇

總結

性能優化涉及了應用的方法面面,很難一言以蔽之。本文咱們主要討論了性能優化的兩大主題 —— 流暢性和內存優化,並分別介紹了他們的檢測方法和優化策略。另外,咱們在優化的同時也要增強優化的證實,用數聽說話。最後,我強烈推薦你們嘗試一下 Observatory 這個工具,開發中若是遇到了奇怪的問題,沒準它能幫你找到答案。

參考

本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@akindone

相關文章
相關標籤/搜索