天天看點

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

作者:閑魚技術-三莅

背景

閑魚用戶端的flutter頁面已經服務上億級使用者,這個時候Flutter頁面的使用者體驗尤其重要,完善Flutter性能穩定性監控體系,可以及早發現線上性能問題,也可以作為使用者體驗提升的衡量标準。那麼Flutter的性能到底如何?是否像官方宣傳的那麼絲滑?Native的性能名額是否可以用來檢測Flutter頁面?下面給大家分享我們在實踐中總結出來的Flutter的性能穩定性監控方案。

目标

過度的丢幀從視覺上會出現卡頓現象,展現在使用者滑動操作不流暢;頁面加載耗時過長容易中斷操作流程;Flutter部分exception會導緻發生異常代碼後面的邏輯沒有走到進而造成邏輯bug甚至白屏。這些問題很容易考驗使用者耐心,引起使用者反感。

是以我們制定以下三個名額作為線上Flutter性能穩定性标準:

  1. 頁面滑動流暢度
  2. 頁面加載耗時(首屏時長+可互動時長)
  3. Exception率

最終目标是讓這些資料名額驅動Flutter使用者體驗更新。

我們先大概了解下螢幕渲染流程:CPU先把UI對象轉變GPU可以識别的資訊存儲進displaylist清單,GPU執行繪圖指令來執行displaylist,取出相應的圖元資訊,進行栅格化渲染,顯示到螢幕上,這樣一個循環的過程實作螢幕重新整理。

閑魚用戶端采用的Native、Flutter混合技術方案,Native頁面FPS監控采用集團高可用方案,Flutter頁面是否可以直接采用這套方案監控?

普遍的FPS檢測方案Android端采用的是Choreographer.FrameCallBack,IOS采用的是CADisplayLink注冊的回調,原理是類似的,在每次發出Vsync信号,并且CPU開始計算的時候執行到對應的回調,這個時候表示螢幕開始一次重新整理,計算固定時間内螢幕渲染次數來得到fps。(這種方式隻能檢測到CPU卡頓,對于GPU的卡頓是無法監控到的)。由于這兩種方法都是在主線程做檢測處理,而flutter的螢幕繪制是在UI TaskRunner中進行,真正的渲染操作是在GPU TaskRunner中,關于詳細的Flutter線程問題可以參考閑魚之前的文章:

深入了解Flutter引擎線程模式

這裡我們得出結論:Native的FPS檢測方法并不适用于Flutter。

Flutter官方給我們提供了 Performance Overlay (具體參考

Flutter performance profiling

) 作為檢測幀率工具,可否直接拿來用?

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

上圖顯示了Performance Overlay模式下的幀率統計,可以看到,Flutter分開計算GPU 和UI TaskRunner。UI Task Runner被Flutter Engine用于執行Dart root isolate代碼,GPU Task Runner被用于執行裝置GPU的相關調用。通過對flutter engine源碼分析,UI frame time是執行window.onBeginFrame所花費的總時間。GPU frame time是處理CPU指令轉換為GPU指令并發送給GPU所花費的時間。

這種方式隻能在debug和profile模式下開啟,沒有辦法作為線上版本的fps統計。但是我們可以通過這種方式獲得啟發,通過監聽Flutter頁面重新整理回調方法handleBeginFrame()、handleDrawFrame()來計算實際FPS。

具體實作方式:

注冊WidgetsFlutterBinding監聽頁面重新整理回調handleBeginFrame()、handleDrawFrame()

handleBeginFrame: Called by the engine to prepare the framework to produce a new frame.
handleDrawFrame: Called by the engine to produce a new frame.           

通過計算handleBeginFrame和handleDrawFrame之間的時間間隔計算幀率,主要流程如下圖:

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

效果

到這裡,我們完成Flutter中頁面幀率的統計,這種方式統計的是UI TaskRunner中的CPU操作耗時,GPU操作在Flutter引擎内部實作,要修改引擎來監控完整的渲染耗時,我們目前大部分的場景沒有複雜到gpu卡頓,問題主要還是集中在CPU,是以說可以反應出大部分問題。從線上資料來看,release模式下Flutter的流暢度還是蠻不錯的,ios的主要頁面均值基本維持在50fps以上,android相對ios略低。這裡需要注意的是幀率的均值fps在反複滑動過程中會有一個稀釋效果,導緻一些卡頓問題沒有暴露出來,是以除了fps均值,需要綜合掉幀範圍、卡頓秒數、滑動時長等資料才能反應出頁面流暢度情況。

頁面加載時長

集團内部高可用方案統計Native頁面加載時長是通過容器初始化後開啟定時器在容器layout的時候檢查螢幕渲染程度,計算可見元件的螢幕覆寫率,滿足條件水準>60%,垂直>80%以上認為滿足頁面填充程度,再檢查主線程心跳判斷是否加載完成

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

再來看看weex頁面加載流程和統計資料的定義

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

Weex的頁面重新整理穩定定義:螢幕内view渲染完成且view樹穩定的時間

具體實作:當螢幕内發生view的add/rm操作時,認為是可互動點,記錄資料。直到沒有再發生為止。

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

在概念上Flutter和weex的首屏時長和可互動時長并不完全一緻,Flutter之是以選擇從路由跳轉開始計算時長主要是因為這種計算方式更貼近使用者體驗,可以擷取更多的問題資訊,比如路由跳轉的時長問題等。

Flutter的可互動時長end點采用的算法與native一緻,可見元件滿足頁面填充程度并且完成心跳檢查的情況下任務可互動,另外對于一些比較空的頁面,元件面積小,無法達到水準>60%,垂直>80%的條件,就用互動前最後一次Frame重新整理時間點作為end點。

具體流程如下圖:

走近科學,探究阿裡閑魚團隊通過資料提升Flutter體驗的真相

由于debug模式采用的JIT編譯,debug模式下體驗加載時長偏長,但是release模式下的AOT編譯時長明顯縮短很多,整體頁面加載時長還是要優于weex。

Flutter部分exception/error 會導緻代碼後面的邏輯沒有走到造成頁面或邏輯bug,是以flutter的exception需要作為穩定性的标準之一

定義

FlutterException率 = exception發生次數 / flutter頁面PV

分子:exception發生次數(已過濾掉白名單)

Flutter内部assert、try-catch和一些異常邏輯的地方會統一調用FlutterError.onError

通過重定向FlutterError.onError到自己的方法中監測exception發生次數,并上報exception資訊

分母:flutter頁面PV

具體實作如下:

Future<Null> main() async {
  FlutterError.onError = (FlutterErrorDetails details) async {
    Zone.current.handleUncaughtError(details.exception, details.stack);
  };
 
  runZoned<Future<Null>>(() async {
    runApp(new HomeApp());
  }, onError: (error, stackTrace) async {
    await _reportError(error, stackTrace);
  });
}           

其中,FlutterError.onError隻會捕獲Flutter framework層的error和exception,官方建議将這個方法按照自己的exception捕獲上報需求定制。在實踐過程中,我們遇到很多不會對使用者體驗産生任何影響的exception會被頻繁觸發,這類沒有改善意義的exception可以添加白名單過濾上報。

有了線上exception的監控,可以及早發現隐患,擷取問題堆棧資訊,友善定位bug,提示整體穩定性

總結

到這裡,我們完成Flutter頁面滑動流暢度、頁面加載時長和Exception率的統計,對于Flutter的性能有一個具體的數字化标準,對以後的使用者體驗提升和性能問題排查提供基礎。目前閑魚用戶端的商品詳情頁和主釋出頁已經全量Flutter化,感興趣的同學可以體驗下這兩個頁面和其他頁面的性能差異,最後歡迎大家提供回報和建議。