Flutter 學習與性能優化總結

前言

??有幸負責的模塊使用Flutter編寫,在三個月的開發過程中,在原有Demo自學基礎上又學到了很多,謹以此篇文章做一個Flutter階段性的學習和總結,以便于往后的學習過程中溫故而知新,那么我們正篇開始。

前世今生

??新事物的誕生往往是有一定原因存在的,移動端在這條路上有幾個階段,從Android Native 到 WebView 階段,為了獲得不發版本就可以獲得實時動態化的效果,雙端使用JSBridge實現了與原生Native底層能力的對接。
??Native容器階段,主要有React Native為代表,雖然RN依賴于原生渲染,性能好與H5,但是還存在一些問題,比如:JavaScript和原生通信,部分場景存在通訊瓶頸,容易導致卡頓、不同移動平臺,空間需要單獨維護,原生版本更新后,社區控件更新較慢等問題。

??Flutter階段,Flutter拋棄了原生系統控件和 Webview,使用自研高性能渲染引擎來繪制Widget,預先(AOT)編譯,運行時直接執行Native(arm)代碼,Dart代碼執行(在UI TaskRunner),圖片下載(IO TaskRunner),真正的渲染(GPU TaskRunner),同平臺的通信等(Platform TaskRunner即Native概念下的主線程)是互相隔離的。針對布局等的優化:布局計算時單次樹走動即可完成;Relayout Boundary機制:如果Child 的size是固定的,那么不會因為Child的Relayout導致Parent ReLayout等布局優化,都讓Flutter脫穎而出。

??所以,App引入Flutter,可以有效提高開發效率,避免雙端不一致的現象,縮小開發成本。但是Flutter畢竟不是原生,在代碼寫法上有很多需要注意的地方,文章第三章會主要總結本人在開發和工作中遇到的問題,相比于H5,flutter在動態化方面,支持的并不好。

1 基礎知識

1.1 運行模式

??Debug:Debug模式可以在真機和模擬器上同時運行:會進入所有斷點,包括debugging信息、debugger aids(比如observatory)和服務擴展。優化了快速develop/run循環,但是沒有優化執行速度、二進制大小和部署。命令flutter run就是以這種模式運行的。

??Release : Release模式只能在真機上運行,不能在模擬器上運行:會關閉所有斷言和debugging信息,關閉所有debugger工具。優化了快速啟動、快速執行和減小包體積。禁用所有的debugging aids和服務擴展。這個模式是為了部署給最終的用戶使用。

??Profile:Profile模式只能在真機上運行,不能在模擬器上運行:和Release模式類似,除了啟用了服務擴展和tracing,以及一些為了最低限度支持tracing運行的東西(比如可以使用DevTools)。flutter run --profile 可進入該模式。

??Test :headless test模式只能在桌面上運行:和Debug模式不同的是headless的而且你能在桌面運行。命令flutter test就是以這種模式運行的。

1.2 架構設計

??Framework : 使用dart實現,包括Material Design風格的Widget,Cupertino(針對iOS)風格的Widgets,文本/圖片/按鈕等基礎Widgets,渲染,動畫,手勢等。

??Engine : C++實現,主要包括:Skia,Dart和Text。 Skia是開源的二維圖形庫,提供了適用于多種軟硬件平臺的通用API。

??Embedder : 嵌入層,即把Flutter嵌入到各個平臺上去,這里做的主要工作包括渲染Surface設置,線程設置,以及插件等。 從這里可以看出,Flutter的平臺相關層很低,平臺(如iOS)只是提供一個畫布,剩余的所有渲染相關的邏輯都在Flutter內部,這就使得它具有了很好的跨端一致性。

1.3 渲染流程

??Widget:Widget樹實際上是一個配置樹,而真正的UI渲染樹是由Element構成;不過,由于Element是通過Widget生成,所以它們之間有對應關系,我們可以寬泛地認為Widget樹就是指UI控件樹或UI渲染樹。

??Element : 一個Widget對象可以對應多個Element對象。這很好理解,根據同一份配置(Widget),可以創建多個實例(Element)。

??從創建到渲染的大體流程是:根據Widget生成Element,然后創建相應的RenderObject并關聯到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。

舉個栗子?

??以下例子取自 閑魚技術博客 閑魚Flutter復雜業務優化

Container(
    color: Colors.blue,
    child: Row(
        children: [
    Image.asset('image'),
    Text('text'),
    ],
  ),
);
在這里插入圖片描述

??依據上圖 來說就是 UI 刷新的時候,Framework 通知 Engine,Engine 會等到下個 Vsync 信號到達的時候,會通知 Framework 進行 animate, build,layout,paint,最后生成 layer 提交給 Engine。Engine 會把 layer 進行組合,生成紋理,最后通過 Open Gl 接口提交數據給 GPU, GPU 經過處理后在顯示器上面顯示

??在基礎能力和業務開發完成后,性能優化和代碼檢查也是非常重要的,所以后邊的文章主要圍繞 性能檢測和優化 等細節的總結。

3 性能檢測工具

結合 Flutter 性能分析 鏈接 和Flutter 性能視圖 等官方文檔,整合調試工具說明,并添加自我理解

3.1 Performance Overlay

??開啟方式比較多,通過 DevTools TimeLines 或者 run Flutter Inspector 都可以進入

在這里插入圖片描述

??點擊show CPU GPU 圖后


在這里插入圖片描述

● 豎軸表示耗時,沿豎軸的黑線是時間線 (間隔單位為 16ms)
● 橫軸則表示幀,垂直的綠色條代表的是當前幀 (如果為紅色則代表處理耗時點)
● 上圖顯示GPU線程消耗的時間 (幀為紅色:Widget太多 場景復雜 渲染耗時)
● 下圖顯示UI線程消耗的時間 (幀為紅色:Dart代碼有問題 查看build中是否做太多耗時操作)

也可以在代碼中開啟 PerformanceOverlay 控件

@override
  Widget build(BuildContext context) {
    Widget app = MaterialApp(
        // true 展示PerformanceOverlay
      showPerformanceOverlay: showPerformanceOverlay,
      title: 'appname',
      theme: ThemeData(
        appBarTheme: AppBarTheme(brightness: Brightness.light),
        textTheme: textTheme
      ),
      home: _buildHome(context),
      builder: (_, widget) {
        return MediaQuery(
          data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
          child: widget
        );
      },
    );

    return WillPopScope(
      child: app,
      onWillPop: dealWillPop,
    );
  }

??showPerformanceOverlay 為MaterialApp自帶屬性,如果沒有使用MaterialApp,可以在代碼中通過PerformanceOverlay.allEnabled(checkerboardOffscreenLayers: true); 開啟

3.2 DevTools TimeLine

??使用 DevTools 可以在Performance Overlay大粒度卡頓范圍內深度挖掘
由于Observatory和DevTools功能類似,且正逐漸被DevTools替代,這里主要羅列DevTools的使用方式。

在這里插入圖片描述

● 頂部部分列表類似于PerformanceOverlay ,深色代表GPU耗時,淺紅色代表UI層耗時(圖層過多)
● 中間部分為幀事件圖表,分位 UI 和 GPU兩部分組成
● 底部部分為CPU圖表,主要有三種不同呈現方式來展示CPU調用堆棧信息

??我們也可以通過添加一些系統api ,更方便我們定位事件圖表具體執行位置
debugProfileBuildsEnabled - 向 Timeline 事件中添加 build 信息
debugProfilePaintsEnabled - 向 timeline 事件中添加 paint 信息
debugPrintRebuildDirtyWidgets - 記錄每幀重建的 widget

??和Android Profiler 類似,幀事件橫軸代表事件順序,縱軸 代表方法調用堆棧,我們可以通過事件寬度來判斷那些事件是比較耗時的。

3.3 Widget rebuild stats

??Android Studio 中 View > Tool Windows > Flutter Performance 打開性能工具窗口,在 Widget rebuild stats 中勾選 Track widget rebuilds 來查看 widget 的重建信息。重建信息包括 Widget 名字、源碼位置、上一幀中重建次數、當前頁面中重建次數。此外,Widget 名字前面還會顯示一個小圖標。
● 黃色旋轉圓圈 - 重建次數過多
● 灰色圓圈 - 未重建
● 灰色旋轉圓圈 其他情況

4 性能優化

??上一章,對于檢測工具大概的總結了使用方式和看法,下邊我們就根據工具,來定位界面的問題,并總結出我的優化方式。

4.1 列表優化

??在商品2.0中列表占比是比較多得,首頁列表的交互邏輯更為復雜,如何讓用戶用起來如絲般順滑,需要進行一些性能的打磨。

問題:
界面滑動的時候 會有輕微掉幀現象,檢測 fps長期飄紅(小于 60fps)


在這里插入圖片描述

(圖一 Flutter Performance 截圖)

在這里插入圖片描述

(圖二 DevTools Fps ui GPU 截圖)

方案:

??更換PowerScrollView 后列表 滑動FPS和部分DevTools截圖
PowerScrollView :概念鏈接

PowerScrollView 優化點:

  1. 針對PowerDataManager 數據可以做到增量更新。
  2. 添加緩存數組,監控_childElements方法來對element 進行緩存,當滾動超出 viewport 的顯示以及預加載范圍或者數據源發生變化,會通過調用 collectGarbage 方法回收不需要的 elements)
  3. cell 層面引入了 placeholder 的機制,快速滑動場景優化build工作量
  4. 更多樣的瀑布流 、列表樣式可供選擇等等優化

引入后效果,

在這里插入圖片描述

在這里插入圖片描述

(圖一 Flutter performance 截圖)

圖中紅色部分主要處于手指第一次按下,并且執行了頂部商品工具欄動畫的原因

在這里插入圖片描述

(圖二 DevTools FPS UI GPU 截圖)

??上圖可以看到 gpu占比較多,列表滑動起始部分還是有卡頓部分,主要是由于首頁商品工具欄過度動畫 和 首頁復雜的UI展示引起的,后續可以通過

4.2 優化 ClipPath 和 ClipRPath

??在剛開始調試初期,使用 Timeline 查看渲染線程性能消耗,可以發現有多個 ClipRectLayer 和 ClipRRectLayer過程,對比商品首頁界面后,猜測是在Flutter殼工程中使用自帶Image展示圖片的時候,對于大圖沒有進行裁剪顯示,部分圓角Widget也需要優化,同時修復 radius 為0也會設置 ClipRRect 的問題。

4.3 常用優化方法

  1. 盡量將setState放在葉子節點,好處是build時影響范圍極小,局部刷新
  2. 使用ListView.builder()而不是直接使用ListView()來構建列表 (官方推薦)
  3. 對于頻繁更新的控件,使用RepaintBoundary隔離,讓其擁有一個獨立的paint區域
  4. 使用const來修飾永遠不需要變更的控件,如果寬高固定,推薦固定寬高,避免重復計算
  5. 按需使用StateLessWidget,并非全部用StateFulWidget
  6. 使用Visibility控件,避免布局樹頻繁切換
  7. 針對于使用Fish-redux,state 對象中的視圖數據真正發生變化的時候,新建 state 對象
  8. 使用圖片替換半透明效果,減少saveLayer 或者clipPath 的使用。

最后

不知不覺已經寫到了最后,對于Flutter的學習和使用,一直都在路上,文章中不免有些紕漏,還望大佬們海涵并指出,后續會繼續深耕Flutter 動態化方案和Flutter性能優化,繼續努力學習,繼續滿血沖沖沖!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容