Flutter 異常處理方案——灰度與降級

airingursb發表於2021-03-12

1. Flutter 異常概述

關於 Flutter 異常型別與捕獲的文章網上已經有許多了,本文不再詳細贅述,此處僅做個小結以保證文章的完整性。

Flutter 異常具體可分為以下幾類:

  • Dart 異常
    • App 異常
      • 同步異常
      • 非同步異常
    • Framework 異常
  • Engine 異常

所謂 Dart 異常,根據來源又可以細分為 App 異常和 Framework 異常,而 App 異常指的是。根據異常程式碼的執行時序,App 異常可以分為兩類,即同步異常和非同步異常:

  • 同步異常可以通過 try-catch 機制捕獲
  • 非同步異常則需要採用 Future 提供的 catchError 語句捕獲

而在 Flutter 中提供了 Zone.runZoned 方法,在 Dart 中,Zone 表示一個程式碼執行的環境範圍,類似於沙盒,可以使用其提供的 onError 回撥函式來攔截所有未被捕獲的異常。因為無論是同步異常還是非同步異常都可以被攔截到,所以我們經常在 runApp 層來捕獲所有的 App 異常。

runZoned<Future<Null>>(() async {
  runApp(MyApp());
}, onError: (error, stackTrace) async {
 //Do sth for error
});
複製程式碼

所謂 Framework 異常,一般是 Widget 在 build 時丟擲的github.com/flutter/flu…,其中預設的 ErrorWidget 就是開發時報錯的紅屏頁面,它也支援被重寫。

業務中,我們可以通過註冊 FlutterError.onError 的回撥來攔截 Flutter framework 外拋的異常:

FlutterError.onError = (FlutterErrorDetails details) {
	reportError(details.exception, details.stack);
};
複製程式碼

所謂 Flutter Engine 異常,以 Android 為例就是 libflutter.so 發生錯誤,對應到 iOS 就是 Flutter.framework 發生錯誤,這部分的錯誤我們直接交給平臺側崩潰收集的 SDK 來處理,比如 firebase crashlytics、bugly 等,後文再詳解。

2. 灰度策略

出於對線上業務的敬畏和某些運營要求,為了保障運營的穩定性,對於線上的 Flutter 業務,我們也需要提供一套較為完備的灰度策略和降級方案。首先,本小節中先談談灰度策略。

灰度的邏輯流程較為簡單:配置灰度策略——後臺下發配置&客戶端載入配置——客戶端處理配置。

2.1 灰度策略配置

我們在內部的配置平臺上定義了一些 Flutter 灰度所需要的配置欄位,具體包括:

  • key:對應的 Flutter 頁面(route)
  • appkey:該配置對應的宿主 App
  • minVersion:最小生效版本
  • maxVersion:最大生效版本
  • type:灰度策略,具體包括尾號灰度,地域灰度,裝置禁用,系統禁用,混合模式,白名單模式等,其中白名單模式出於測試考慮,混合模式則是支援配置各種策略取並集生效。
  • action:生效範圍,如全量生效,全量不生效,灰度生效等。
  • url:降級的連結,支援引數替換符寫法,客戶端能夠將 Flutter route 的入參拼接成 url query parameters。

2.2 後臺下發與客戶端載入配置

冷熱啟動都會拉配置,考慮到失敗會有3次重試,本地會維護一份單例,在業務側要開啟 Flutter 頁面時都需要檢查灰度配置,來決定是否開啟 Flutter 頁面。 當然為了拉配置時防止 3 次重試都失敗了,發版的時候本地會存一份各 Flutter 頁面的降級配置 Map,極端場景下,會自動開啟降級。

2.3 客戶端處理配置

在業務側要開啟 Flutter 頁面時都需要檢查灰度配置,來決定是否開啟 Flutter 頁面。若判斷非灰度,即命中降級,則拉配置的降級連結,配好 url 引數後使用 WebView 開啟降級後的 H5。

需要注意的是,我們目前的業務基本都是 H5 改 Flutter,所以預設都有降級版本,而且降級版本的可靠性是可以保證的。對於未來只上 Flutter 的新業務,我們也正在預研 Flutter Web 的同構方案。

3. 降級方案

我們需要及時的降級來保證 Flutter 業務的可靠性,灰度和降級其實本質上都是來區分業務是使用 Flutter 還是 H5,只是前者是手動配置的,後者是自動生效的。在本地會維護根據 App 版本來維護一份降級配置,開啟頁面前會檢查是否需要降級。有以下幾種需要及時降級的場景:

3.1 未命中灰度降級

如前文所述,若業務方配置了灰度策略配置,在未命中灰度降級的場景下開啟對應的 Flutter 頁面,該頁面需要降級並做上報。

3.2 框架異常降級

如果捕獲到 Flutter Framework 異常,則將該頁面置為「需要降級」,提供自定義的 ErrorWidget 提醒使用者頁面出錯需要重新進入,之後在使用者下次進入該頁面時觸發降級,定向到 H5 頁面。

而對於 Dart 異常,由於Dart 採用事件迴圈的機制來執行任務,所以各個任務的執行狀態是互相獨立的。也就是說發生異常只會導致當前任務後續的程式碼不會被執行,使用者仍可以繼續使用頁面中的其他功能,影響面不會太大,此處沒有去強制降級處理,僅僅做了錯誤上報。

3.3 引擎崩潰降級

但如果是引擎發生了錯誤必定會導致 App Crash,這種情況下不僅需要上報日誌,也會置好標誌位,在使用者下次開啟 App 時不再啟動 Flutter Engine,並全量降級 Flutter 所有頁面。

3.4 產物載入失敗降級

技術上我們使用了定製引擎並做了 Flutter 產物裁剪,每次發版時 App.framework 中會存一份對應的減包 zip 的 md5 值,在使用者首次啟動 App 時會下載減包產物再去啟動引擎,之後校驗產物完整性無問題後,定製引擎再去載入減包產物。但是存在著產物下載失敗的情況,除了階段性重試以外,這種情況也不能啟動 Flutter Engine,並做所有頁面的全量降級並上報。

3.5 Flutter 相關崩潰降級

除此以外,我們也遇到過 Flutter 導致的崩潰,不屬於引擎崩潰也不是產物載入問題,也不是 Flutter 異常,僅僅是 Flutter Plugin 的問題,如外掛原生側的實現邏輯問題導致的崩潰,這也屬於 Flutter 相關崩潰,但是在 Bugly 上報的日誌中無法找到 Flutter 字樣,因為程式退出時並非中斷在 Flutter 內部或者引擎側。

對於這種情況,我們會記錄崩潰或 ANR 上報時的 topViewController 並溯源路徑,如果當前路由棧記憶體在 Flutter Activity 或者 FlutterViewController,保險起見,發生崩潰依然降級。

4. 運營日報

Flutter 運營日報資料來源為效能上報和異常上報。而至於崩潰的監控和告警,我們則交給了客戶端的 Bugly 來處理了。日報記錄了各 Flutter 頁面在不同 App 版本的表現情況,有以下幾個指標供讀者參考:

  • pv
  • 訪問成功率
  • Crash 率,Crash 影響使用者數
  • 秒開率(300ms界限)
  • 降級率,灰度率
  • ……

最後,結合產物動態載入與降級策略的啟動流程圖如下所示:

啟動流程圖 [image:BF00167C-0418-41DD-B837-F57C28C2C054-57904-000150D1B3F35050/flutter-start-flowchart-bg.png]

參考文章

相關文章