Flutter性能優(yōu)化實踐之Timeline

前言

Flutter自誕生之時就以輕松構建美觀、高性能組件著稱,目標是提供逼近“原生性能”的60幀每秒(fps)的性能,或者是在可以達到120Hz的設備上提供120fps的性能。這里的幀率fps是指的畫面每秒傳輸幀數(shù),是衡量性能優(yōu)化中屏幕是否卡頓的一個重要指標,如何測量一個應用的幀率,就要用到工具Timeline。

注:關于Performance性能指標的描述有多個方面,本文側重點為Timeline

Flutter 性能分析性能

flutter 支持三種模式編譯的app,處于開發(fā)的不同階段,使用不同模式下的app

調(diào)試模式

在命令行下輸入flutter run,默認會啟動debug模式,該模式下app使用JIT的編譯方式,運行時去解析執(zhí)行程序,意味著應用有著較慢的性能體驗,比如冷啟動或者第一次初始化Flutter Engine會有較長時間的黑屏、使用過程中掉幀和卡頓這都屬于正?,F(xiàn)象。

該模式下一個突出的特點是可以熱重載,所以更像開發(fā)一個前端應用

Release模式

命令行輸入flutter run --release 會使用 Release 模式來進行編譯,該模式下應用具有最大的優(yōu)化和性能體驗,采用AOT的編譯技術,由dart本地虛擬機將代碼編譯成對應平臺例如Android、IOS對應的機器碼,相當于原生開發(fā),編譯耗時,失去了熱重載,但具有良好性能。一般用于最后應用市場發(fā)包,失去了debug模式下的各種調(diào)試應用功能

Profile模式

命令行輸入flutter run --profile會使用Profile模式編譯,一個專門的調(diào)試應用性能模式,該模式是在保留一部分調(diào)試能力的基礎上,又較大程度還原app真實性能,所以該模式不建議在虛擬機或模擬器上運行,因為無法真實代表真機性能。

輸入該命令,運行完以后控制臺會打印調(diào)試的地址

image

點擊即可跳轉(zhuǎn)到調(diào)試頁

image

這里選擇timeline,點擊Flutter Developer按鈕,即可進入timeline的調(diào)試頁面,在手機上操作幾秒鐘,點擊右上角Refresh按鈕,即可加載出圖像,看頁面代碼應該也是臨時借用的systemtrace的,操作都類似

image

這個其實是以前舊版的調(diào)試方式,現(xiàn)在雖然也能使用,但是實際測試中發(fā)現(xiàn)不太準確,使用也不方便,現(xiàn)在基本不使用這種方式了,新版本的Performance頁面很美觀使用也方便,可以在Android studio中使用Flutter Performance插件中頁面粗略判斷timeline是否卡頓,也可以打開Flutter Performance右下角Open DevTools按鈕在網(wǎng)頁上具體分析。

Flutter Inspector

這里順便說下Android Studio中其他兩個Flutter 插件,一個是Flutter Outline,即顯示頁面布局的大綱,可以快速查看頁面布局的樹形結構,菜單欄提供了包裹、刪除、上下移動組件的快捷功能,很簡單這里不詳細介紹。另一個插件是Flutter Inspector,安裝Flutter插件后,AS右側邊欄會出現(xiàn)這個標簽,主要作用是開發(fā)過程中布局調(diào)試用的,類似Android開發(fā)里的Xml布局查看工具,只不過需要debug模式運行時才可以查看布局,這點相對于原生開發(fā)xml快速定位布局文件的體驗還是差一些,相信后期還會有好的優(yōu)化。

點擊標簽后頁面如下

image

頁面主要分上邊功能按鈕、左邊View樹、右邊布局預覽。

每個按鈕的作用:

Select Widget Mode

image

選擇組件模式,選中后點擊下方View Tree中某個Widget自動定位到代碼位置,可在開發(fā)中快速定位代碼

Refresh Tree

image

刷新View Tree,在App上跳轉(zhuǎn)其他頁面后,View Tree不自動更新,所以有了此按鈕

Slow Animations

image

放慢動畫

Debug Paint

image

顯示布局測量,可以快速確定組件邊界,效果如下


image

Show Paint Baselines

image

顯示Text組件的Baseline,方便文字對齊

Show Repaint Rainbow

image

顯示重繪時顏色變化

Invert Oversized Images

image

輕松查看分辨率比顯示分辨率高的圖片

image

Flutter Performance

點擊右邊欄Flutter Performance,出現(xiàn)如下頁面:

image

左上角第一個按鈕是在手機上顯示performance Overlay,效果如下,其他按鈕和上邊Inspector中一樣

image

上邊可以粗略判斷App是否掉幀,白色正常,紅色就卡頓了,中間內(nèi)存占用,下邊是每個組件的重繪狀態(tài),點擊右下角的Open DevTools可以使用更多功能

Track widget rebuilds復選框勾上可以方便的查看頁面中組件的重繪狀態(tài),對于不應該重繪的組件應該調(diào)整代碼層級結構或者抽離組件的方式避免重繪造成性能的損失,這里分享個人在開發(fā)中總結的幾點經(jīng)驗:

  • 盡量少使用StatefulWidget編寫大的頁面,盡量避免在StatefulWidget中使用setState
  • 不需要重繪組件添加const關鍵字
  • Provider刷新機制時使用Consumer下沉刷新范圍
  • 小部件需要刷新抽取成StatefulWidget,縮小刷新范圍

Timeline

時間線事件圖表顯示了應用程序中的所有事件跟蹤。 Flutter框架在構建框架,繪制場景以及跟蹤其他活動(例如HTTP流量)時會發(fā)出時間軸事件。這些事件顯示在時間軸上。您還可以通過dart發(fā)送自己的時間線事件:developer TimelineTimelineTask API

Timeline 事件軌跡的格式和查看器并被許多其他項目使用,此類項目包括 Chromium & Android (via systrace).

軌跡記錄的形式是JSON文件格式存儲的,點擊右上角的Export按鈕可以導出文件。

打開DevTools以后,在App上操作一段,點擊左上角Refresh按鈕即可加載出如下圖所示時間線。


image

圖中藍色條是正常幀,紅色條是卡頓幀,鼠標移動到紅條上可以查看當前卡頓幀的耗時,右上角有不同顏色條的對應關系,分別有UI、Raster、Jank。

UI

UI線程在Dart VM中執(zhí)行Dart代碼。這包括您的應用程序以及Flutter框架中的代碼。當您的應用創(chuàng)建并顯示場景時,UI線程將創(chuàng)建一個層樹(包含與設備無關的繪畫命令的輕量級對象),并將該層樹發(fā)送到要在設備上呈現(xiàn)的柵格線程。不要阻塞該線程。

Raster

光柵線程(以前稱為GPU線程)執(zhí)行Flutter Engine中的圖形代碼。該線程獲取層樹并通過與GPU(圖形處理單元)對話來顯示它。您無法直接訪問柵格線程或其數(shù)據(jù),但是如果該線程速度很慢,則是由于您在Dart代碼中所做的操作所致。圖形庫Skia在此線程上運行。

有時,場景會產(chǎn)生易于構造的圖層樹,但是在柵格線程上渲染的樹代價很高。在這種情況下,您需要弄清楚代碼正在做什么,這會導致渲染代碼變慢。對于GPU而言,特定種類的工作負載更加困難。它們可能涉及對saveLayer()的不必要調(diào)用,與多個對象相交的不透明性以及在特定情況下的剪輯或陰影

Jank

幀渲染圖顯示帶有紅色疊加層的垃圾幀。如果一個幀完成的時間超過約16毫秒(對于60 FPS設備),則該幀被認為是過時的。為了達到60 FPS(每秒幀)的幀渲染速率,每個幀必須在約16 ms或更短的時間內(nèi)渲染。錯過此目標時,您可能會遇到UI混亂或掉幀的情況

Render Frames

當一個Flutter應用或者Flutter Engine啟動時,它會啟動(或者從池中選擇)另外三個線程,這些線程有些時候會有重合的工作點,但是通常,它們被稱為UI線程,GPU線程,IO線程。UI、GPU之間的工作流程如下:

image

為了生成一幀,F(xiàn)lutter engine首先裝備了vsync鎖存器,一個vsync的事件將會指示Flutter engine開始一些工作并最終繪制出新的幀呈現(xiàn)在屏幕上,vsync事件的生成頻率會根據(jù)硬件平臺的刷新率決定。

vsync首先會喚醒UI線程,UI線程的工作是將你代碼中編寫的Widget樹轉(zhuǎn)化為要渲染的RenderTree,F(xiàn)lutter中有三顆樹的概念WidgetTree,ElementTree,RenderTree,dart文件中的Widget樹并不是最終參與繪制的,而只是方便開發(fā)者編寫頁面的一個配置。比如,我們指定這里有一個縱向列表Column,列表里有三個并列Text,然后Flutter會根據(jù)相應語義在對應位置生成對應Element,這才是真正意義上的Flutter UI組件,也是顯示到屏幕上的元素。

組件樹對應到屏幕上還要經(jīng)過一層渲染樹(RenderObject)的轉(zhuǎn)化,RenderObject是實際的渲染對象它負責布局測量以及繪制操作,這樣做的目的是為了更好的應對上層UI的頻繁變化,盡可能地去比較更新,修改配置而不是直接創(chuàng)建下層樹,因為RenderObject樹的創(chuàng)建開銷比較大,所以Widget重新創(chuàng)建,ElementTree和RenderTree并不會完全重新創(chuàng)建,而是會復用一些節(jié)點,提升性能。UI線程工作到生成RenderTree的過程叫做渲染樹

一旦創(chuàng)建了渲染樹,GPU線程就會被喚醒,這個線程的工作是將渲染樹的信息轉(zhuǎn)換到GPU的命令緩沖區(qū),然后在同一線程將數(shù)據(jù)提交給GPU執(zhí)行

示例

模擬一個組件耗時操作


class PageOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.tealAccent,
      child: Center(
        child: ListView(
          children: [
            for(var i=0;i<100000;i++) _buildItemWidget(i),
          ],
        ),
      ),
    );
  }

  Widget _buildItemWidget(int i) {
    var line = lines[i % lines.length];
    return Padding(
        padding: EdgeInsets.symmetric(vertical: 12,horizontal: 18),
      child: Row(
        children: [
          Container(
            color: Colors.black,
            child: SizedBox(
              width: 30,
              height: 30,
              child: Center(
                child: Text(
                  line.substring(0,1),
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
          SizedBox(width: 10,),
          Expanded(child: Text(
            line,
            softWrap: false,
          ))
        ],
      ),
    );
  }
}

可以看到,在ListView的children填充時,沒有復用布局,模擬了一個重復創(chuàng)建十萬條子child的情況,頁面第一次加載時會看到明顯卡頓,timeline顯示如下,一條明顯的紅線,就是掉幀發(fā)生的位置。

image

點擊Jank發(fā)生的位置,可以看到Timeline Events對應的事件被選中,下方有各個方法執(zhí)行的耗時時間,可以看到_buildItemWidget方法耗時26.81ms發(fā)生掉幀

模擬一個方法耗時


class PageTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.greenAccent,
      child: Center(
        child: Text(
          '第2頁 ' + _fibonacci(30).toString(),
          style: TextStyle(color: Colors.black, fontSize: 20.0),
        ),
      ),
    );
  }

  static int _fibonacci(int i) {
    if(i <= 1) return i;
    return _fibonacci(i - 1) + _fibonacci(i - 2);
  }
}

image

組建初始化時,執(zhí)行一個斐波那契函數(shù)遞歸調(diào)用,時間復雜度為O(2n),傳入?yún)?shù)30,即函數(shù)運行230次運算,初始化頁面可看到明顯卡頓,定位耗時方法同上。

示例代碼已上傳至github

拓展

這里給大家推薦一個小工具fps_monitor,貝殼同學開源的檢測頁面流暢度的小工具,可以更直觀和量化的評估頁面流暢度,頁面大概長這樣

image

最大耗時平均耗時可以直觀的觀測頁面優(yōu)化前后對比效果。

頁面流暢度劃為了四個級別:流暢(藍色)、良好(黃色)、輕微卡頓(粉色)卡頓(紅色),將 FPS 折算成一幀所消耗的時間,不同級別采用不一樣的顏色,統(tǒng)計不同級別出現(xiàn)的次數(shù)

具體可以跳轉(zhuǎn)鏈接:
https://juejin.cn/post/6947911434424549384

總結

性能優(yōu)化在任何平臺任何語言上都是永恒不變的話題,理解性能優(yōu)化原理,提升觀察的敏銳性對一個開發(fā)者至關重要。利用Flutter提供的插件和性能分析工具,能夠幫助我們快速的定位到問題代碼,提升開發(fā)效率,F(xiàn)lutter Inspector可以在寫代碼階段提升頁面編碼質(zhì)量,Timeline可以在運行階段發(fā)現(xiàn)哪個頁面掉幀嚴重,重點分析。

參考鏈接

https://medium.com/flutter/profiling-flutter-applications-using-the-timeline-a1a434964af3

https://cloud.tencent.com/developer/article/1614400

https://juejin.cn/post/6940134891606507534

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 1.前言 flutter_deer這個項目開源也近一年了,目前收獲了3100+的star,這無疑是對這個項目的最大...
    唯鹿_weilu閱讀 2,122評論 3 10
  • 在flutter的開發(fā)和工作中,因為工作內(nèi)容的要求越來越高,加上一位優(yōu)秀的同事,自己也對自己的寫的代碼除了規(guī)范的要...
    iOS弗森科閱讀 2,077評論 0 6
  • ??歡迎前往本人的GitHub查看更多內(nèi)容。點擊前往GitHub 在flutter的開發(fā)和工作中,因為工作內(nèi)容的要求...
    iOS超級洋閱讀 789評論 0 0
  • 表情是什么,我認為表情就是表現(xiàn)出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,934評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 8,205評論 0 4

友情鏈接更多精彩內(nèi)容