<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在上一篇文章中,我與你分享了如何捕獲 Flutter 應用的未處理異常。所謂異常,指的是 Dart 代碼在運行時意外發生的錯誤事件。對于單一異常來說,我們可以使用 try-catch,或是 catchError 去處理;而如果我們想對異常進行集中的攔截治理,則需要使用 Zone,并結合 FlutterError 進行統一管理。異常一旦被抓取,我們就可以利用第三方數據上報服務(比如 Bugly),上報其上下文信息了。 這些線上異常的監控數據,對于開發者盡早發現線上隱患,確定問題根因至關重要。如果我們想進一步評估應用整體的穩定性的話,就需要把異常信息與頁面的渲染關聯起來。比如,頁面渲染過程是否出現了異常,而導致功能不可用? 而對于以“絲般順滑”著稱的 Flutter 應用而言,頁面渲染的性能同樣需要我們重點關注。比如,界面渲染是否出現會掉幀卡頓現象,或者頁面加載是否會出現性能問題導致耗時過長?這些問題,雖不至于讓應用完全不能使用,但也很容易引起用戶對應用質量的質疑,甚至是反感。 通過上面的分析,可以看到,衡量線上 Flutter 應用整體質量的指標,可以分為以下 3 類: * 頁面異常率; * 頁面幀率; * 頁面加載時長。 其中,頁面異常率反應了頁面的健康程度,頁面幀率反應了視覺效果的順滑程度,而頁面加載時長則反應了整個渲染過程中點對點的延時情況。 這三項數據指標,是度量 Flutter 應用是否優秀的重要質量指標。通過梳理這些指標的統計口徑,建立起 Flutter 應用的質量監控能力,這樣一來我們不僅可以及早發現線上隱患,還可以確定質量基線,從而持續提升用戶體驗。 所以在今天的分享中,我會與你詳細講述這 3 項指標是如何采集的。 ## 頁面異常率 頁面異常率指的是,頁面渲染過程中出現異常的概率。它度量的是頁面維度下功能不可用的情況,其統計公式為:**頁面異常率 = 異常發生次數 / 整體頁面 PV 數**。 在了解了頁面異常率的統計口徑之后,接下來我們分別來看一下這個公式中的分子與分母應該如何統計吧。 我們先來看看**異常發生次數的統計方法**。通過上一篇文章,我們已經知道了在 Flutter 中,未處理異常需要通過 Zone 與 FlutterError 去捕獲。所以,如果我們想統計異常發生次數的話,依舊是利用這兩個方法,只不過要在異常攔截的方法中,通過一個計數器進行累加,統一記錄。 下面的例子演示了異常發生次數的具體統計方法。我們使用全局變量 exceptionCount,在異常捕獲的回調方法 \_reportError 中持續地累加捕獲到的異常次數: ~~~ int exceptionCount = 0; Future<Null> _reportError(dynamic error, dynamic stackTrace) async { exceptionCount++; // 累加異常次數 FlutterCrashPlugin.postException(error, stackTrace);} Future<Null> main() async { FlutterError.onError = (FlutterErrorDetails details) async { // 將異常轉發至 Zone Zone.current.handleUncaughtError(details.exception, details.stack); }; runZoned<Future<Null>>(() async { runApp(MyApp()); }, onError: (error, stackTrace) async { // 攔截異常 await _reportError(error, stackTrace); });} ~~~ 接下來,我們再看看**整體頁面 PV 數如何統計**吧。整體頁面 PV 數,其實就是頁面的打開次數。通過第 21 篇文章“[路由與導航,Flutter 是這樣實現頁面切換的](https://time.geekbang.org/column/article/118421)”,我們已經知道了 Flutter 頁面的切換需要經過 Navigator 來實現,所以頁面切換狀態也需要通過 Navigator 才能感知到。 與注冊頁面路由類似的,在 MaterialApp 中,我們可以通過 NavigatorObservers 屬性,去監聽頁面的打開與關閉。下面的例子演示了**NavigatorObserver 的具體用法**。在下面的代碼中,我們定義了一個繼承自 NavigatorObserver 的觀察者,并在其 didPush 方法中,去統計頁面的打開行為: ~~~ int totalPV = 0;// 導航監聽器class MyObserver extends NavigatorObserver{ @override void didPush(Route route, Route previousRoute) { super.didPush(route, previousRoute); totalPV++;// 累加 PV }} class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // 設置路由監聽 navigatorObservers: [ MyObserver(), ], home: HomePage(), ); } } ~~~ 現在,我們已經收集到了異常發生次數和整體頁面 PV 數這兩個參數,接下來我們就可以計算出頁面異常率了: ~~~ double pageException() { if(totalPV == 0) return 0; return exceptionCount/totalPV;} ~~~ 可以看到,頁面異常率的計算還是相對比較簡單的。 ## 頁面幀率 頁面幀率,即 FPS,是圖像領域中的定義,指的是畫面每秒傳輸幀數。由于人眼的視覺暫留特質,當所見到的畫面傳輸幀數高于一定數量的時候,就會認為是連貫性的視覺效果。因此,對于動態頁面而言,每秒鐘展示的幀數越多,畫面就越流暢。 由此我們可以得出,**FPS 的計算口徑為單位時間內渲染的幀總數**。在移動設備中,FPS 的推薦數值通常是 60Hz,即每秒刷新頁面 60 次。 為什么是 60Hz,而不是更高或更低的值呢?這是因為顯示過程,是由 VSync 信號周期性驅動的,而 VSync 信號的周期就是每秒 60 次,這也是 FPS 的上限。 CPU 與 GPU 在接收到 VSync 信號后,就會計算圖形圖像,準備渲染內容,并將其提交到幀緩沖區,等待下一次 VSync 信號到來時顯示到屏幕上。如果在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,這一幀就會被丟棄,等待下一次機會再顯示,而這時頁面會保留之前的內容不變,造成界面卡頓。因此,FPS 低于 60Hz 時就會出現掉幀現象,而如果低于 45Hz 則會有比較嚴重的卡頓現象。 為方便開發者統計 FPS,Flutter 在全局 window 對象上提供了幀回調機制。我們可以**在 window 對象上注冊 onReportTimings 方法**,將最近繪制幀耗費的時間(即 FrameTiming),以回調的形式告訴我們。有了每一幀的繪制時間后,我們就可以計算 FPS 了。 需要注意的是,onReportTimings 方法只有在有幀被繪制時才有數據回調,如果用戶沒有和 App 發生交互,界面狀態沒有變化時,是不會產生新的幀的。考慮到單個幀的繪制時間差異較大,逐幀計算可能會產生數據跳躍,所以為了讓 FPS 的計算更加平滑,我們需要保留最近 25 個 FrameTiming 用于求和計算。 而另一方面,對于 FPS 的計算,我們并不能孤立地只考慮幀繪制時間,而應該結合 VSync 信號的周期,即 1/60 秒(即 16.67 毫秒)來綜合評估。 由于幀的渲染是依靠 VSync 信號驅動的,如果幀繪制的時間沒有超過 16.17 毫秒,我們也需要把它當成 16.67 毫秒來算,因為繪制完成的幀必須要等到下一次 VSync 信號來了之后才能渲染。而如果幀繪制時間超過了 16.67 毫秒,則會占用后續的 VSync 信號周期,從而打亂后續的繪制次序,產生卡頓現象。這里有兩種情況: * 如果幀繪制時間正好是 16.67 的整數倍,比如 50,則代表它花費了 3 個 VSync 信號周期,即本來可以繪制 3 幀,但實際上只繪制了 1 幀; * 如果幀繪制時間不是 16.67 的整數倍,比如 51,那么它花費的 VSync 信號周期應該向上取整,即 4 個,這意味著本來可以繪制 4 幀,實際上只繪制了 1 幀。 所以我們的 FPS 計算公式最終確定為:**FPS=60\* 實際渲染的幀數 / 本來應該在這個時間內渲染完成的幀數**。 下面的示例演示了如何通過 onReportTimings 回調函數實現 FPS 的計算。在下面的代碼中,我們定義了一個容量為 25 的列表,用于存儲最近的幀繪制耗時 FrameTiming。在 FPS 的計算函數中,我們將列表中每幀繪制時間與 VSync 周期 frameInterval 進行比較,得出本來應該繪制的幀數,最后兩者相除就得到了 FPS 指標。 需要注意的是,Android Studio 提供的 Flutter 插件里展示的 FPS 信息,其實也來自于 onReportTimings 回調,所以我們在注冊回調時需要保留原始回調引用,否則插件就讀不到 FPS 信息了。 ~~~ import 'dart:ui'; var orginalCallback; void main() { runApp(MyApp()); // 設置幀回調函數并保存原始幀回調函數 orginalCallback = window.onReportTimings; window.onReportTimings = onReportTimings; } // 僅緩存最近 25 幀繪制耗時 const maxframes = 25; final lastFrames = List<FrameTiming>(); // 基準 VSync 信號周期 const frameInterval = const Duration(microseconds: Duration.microsecondsPerSecond ~/ 60); void onReportTimings(List<FrameTiming> timings) { lastFrames.addAll(timings); // 僅保留 25 幀 if(lastFrames.length > maxframes) { lastFrames.removeRange(0, lastFrames.length - maxframes); } // 如果有原始幀回調函數,則執行 if (orginalCallback != null) { orginalCallback(timings); } } double get fps { int sum = 0; for (FrameTiming timing in lastFrames) { // 計算渲染耗時 int duration = timing.timestampInMicroseconds(FramePhase.rasterFinish) - timing.timestampInMicroseconds(FramePhase.buildStart); // 判斷耗時是否在 Vsync 信號周期內 if(duration < frameInterval.inMicroseconds) { sum += 1; } else { // 有丟幀,向上取整 int count = (duration/frameInterval.inMicroseconds).ceil(); sum += count; } } return lastFrames.length/sum * 60; } ~~~ 運行這段代碼,可以看到,我們統計的 FPS 指標和 Flutter 插件展示的 FPS 走勢是一致的。 :-: ![](https://img.kancloud.cn/a8/07/a807f4338b5a1979f7255ad2a3bb051b_1246x528.png) 圖 1 FPS 指標走勢 ## 頁面加載時長 頁面加載時長,指的是頁面從創建到可見的時間。它反應的是代碼中創建頁面視圖是否存在過度繪制,或者繪制不合理導致創建視圖時間過長的情況。 從定義可以看出,**頁面加載時長的統計口徑為頁面可見的時間 - 頁面創建的時間**。獲取頁面創建的時間比較容易,我們只需要在頁面的初始化函數里記錄時間即可。那么,**頁面可見的時間應該如何統計**呢? 在第 11 篇文章“[提到生命周期,我們是在說什么?](https://time.geekbang.org/column/article/109490)”中,我在介紹 Widget 的生命周期時,曾向你介紹過 Flutter 的幀回調機制。WidgetsBinding 提供了單次 Frame 回調 addPostFrameCallback 方法,它會在當前 Frame 繪制完成之后進行回調,并且只會回調一次。一旦監聽到 Frame 繪制完成回調后,我們就可以確認頁面已經被渲染出來了,因此我們可以借助這個方法去獲取頁面可見的時間。 下面的例子演示了如何通過幀回調機制獲取頁面加載時長。在下面的代碼中,我們在頁面 MyPage 的初始化方法中記錄了頁面的創建時間 startTime,然后在頁面狀態的初始化方法中,通過 addPostFrameCallback 注冊了單次幀繪制回調,并在回調函數中記錄了頁面的渲染完成時間 endTime。將這兩個時間做減法,我們就得到了 MyPage 的頁面加載時長: ~~~ class MyHomePage extends StatefulWidget { int startTime; int endTime; MyHomePage({Key key}) : super(key: key) { // 頁面初始化時記錄啟動時間 startTime = DateTime.now().millisecondsSinceEpoch; } @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override void initState() { super.initState(); // 通過幀繪制回調獲取渲染完成時間 WidgetsBinding.instance.addPostFrameCallback((_) { widget.endTime = DateTime.now().millisecondsSinceEpoch; int timeSpend = widget.endTime - widget.startTime; print("Page render time:${timeSpend} ms"); }); } ... } ~~~ 試著運行一下代碼,觀察命令行輸出: ~~~ flutter: Page render time:548 ms ~~~ 可以看到,通過單次幀繪制回調統計得出的頁面加載時間為 548 毫秒。 至此,我們就已經得到了頁面異常率、頁面幀率和頁面加載時長這 3 個指標了。 ## 總結 好了,今天的分享就到這里,我們來總結下主要內容吧。 今天我們一起學習了衡量 Flutter 應用線上質量的 3 個指標,即頁面異常率、頁面幀率和頁面加載時長,以及分別對應的數據采集方式。 其中,頁面異常率表示頁面渲染過程中的穩定性,可以通過集中捕獲未處理異常,結合 NavigatorObservers 觀察頁面 PV,計算得出頁面維度下功能不可用的概率。 頁面幀率則表示了頁面的流暢情況,可以利用 Flutter 提供的幀繪制耗時回調 onReportTimings,以加權的形式計算出本應該繪制的幀數,得到更為準確的 FPS。 而頁面加載時長,反應的是渲染過程的延時情況。我們可以借助于單次幀回調機制,來獲取頁面渲染完成時間,從而得到整體頁面的加載時長。 通過這 3 個數據指標統計方法,我們再去評估 Flutter 應用的性能時,就有一個具體的數字化標準了。而有了數據之后,我們不僅可以及早發現問題隱患,準確定位及修復問題,還可以根據它們去評估應用的健康程度和頁面的渲染性能,從而確定后續的優化方向。 我把今天分享涉及的知識點打包到了[GitHub](https://github.com/cyndibaby905/40_peformance_demo)中,你可以下載下來,反復運行幾次,加深理解與記憶。 ## 思考題 最后,我給你留一道思考題吧。 如果頁面的渲染需要依賴單個或多個網絡接口數據,這時的頁面加載時長應該如何統計呢?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看