閒魚客戶端的flutter頁面已經服務上億級用戶,這個時候Flutter頁面的用戶體驗尤爲重要,完善Flutter性能穩定性監控體系,能夠及早發現線上性能問題,也能夠做爲用戶體驗提高的衡量標準。那麼Flutter的性能到底如何?是否像官方宣傳的那麼絲滑?Native的性能指標是否能夠用來檢測Flutter頁面?下面給你們分享咱們在實踐中總結出來的Flutter的性能穩定性監控方案。android
過分的丟幀從視覺上會出現卡頓現象,體如今用戶滑動操做不流暢;頁面加載耗時過長容易中斷操做流程;Flutter部分exception會致使發生異常代碼後面的邏輯沒有走到從而形成邏輯bug甚至白屏。這些問題很容易考驗用戶耐心,引發用戶反感。ios
因此咱們制定如下三個指標做爲線上Flutter性能穩定性標準:算法
最終目標是讓這些數據指標驅動Flutter用戶體驗升級。weex
咱們先大概瞭解下屏幕渲染流程:CPU先把UI對象轉變GPU能夠識別的信息存儲進displaylist列表,GPU執行繪圖指令來執行displaylist,取出相應的圖元信息,進行柵格化渲染,顯示到屏幕上,這樣一個循環的過程實現屏幕刷新。併發
閒魚客戶端採用的Native、Flutter混合技術方案,Native頁面FPS監控採用集團高可用方案,Flutter頁面是否能夠直接採用這套方案監控?async
廣泛的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) 做爲檢測幀率工具,能否直接拿來用?性能
上圖顯示了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所花費的時間。spa
這種方式只能在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中頁面幀率的統計,這種方式統計的是UI TaskRunner中的CPU操做耗時,GPU操做在Flutter引擎內部實現,要修改引擎來監控完整的渲染耗時,咱們目前大部分的場景沒有複雜到gpu卡頓,問題主要仍是集中在CPU,因此說能夠反應出大部分問題。從線上數據來看,release模式下Flutter的流暢度仍是蠻不錯的,ios的主要頁面均值基本維持在50fps以上,android相對ios略低。這裏須要注意的是幀率的均值fps在反覆滑動過程當中會有一個稀釋效果,致使一些卡頓問題沒有暴露出來,因此除了fps均值,須要綜合掉幀範圍、卡頓秒數、滑動時長等數據才能反應出頁面流暢度狀況。
集團內部高可用方案統計Native頁面加載時長是經過容器初始化後開啓定時器在容器layout的時候檢查屏幕渲染程度,計算可見組件的屏幕覆蓋率,知足條件水平>60%,垂直>80%以上認爲知足頁面填充程度,再檢查主線程心跳判斷是否加載完成
再來看看weex頁面加載流程和統計數據的定義
Weex的頁面刷新穩定定義:屏幕內view渲染完成且view樹穩定的時間
具體實現:當屏幕內發生view的add/rm操做時,認爲是可交互點,記錄數據。直到沒有再發生爲止。
在概念上Flutter和weex的首屏時長和可交互時長並不徹底一致,Flutter之因此選擇從路由跳轉開始計算時長主要是由於這種計算方式更貼近用戶體驗,能夠獲取更多的問題信息,好比路由跳轉的時長問題等。
Flutter的可交互時長end點採用的算法與native一致,可見組件知足頁面填充程度而且完成心跳檢查的狀況下任務可交互,另外對於一些比較空的頁面,組件面積小,沒法達到水平>60%,垂直>80%的條件,就用交互前最後一次Frame刷新時間點做爲end點。
具體流程以下圖:
因爲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化,感興趣的同窗能夠體驗下這兩個頁面和其餘頁面的性能差別,最後歡迎你們提供反饋和建議。
本文做者:閒魚技術-三蒞
本文爲雲棲社區原創內容,未經容許不得轉載。