Airing

Airing

哲学系学生 / 小学教师 / 程序员,个人网站: ursb.me
github
email
zhihu
medium
tg_channel
twitter_id

Flutter 异常处理方案——灰度与降级

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 時拋出的,其中默認的 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 界限)
  • 降級率,灰度率
  • ……

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

啟動流程圖

參考文章#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。