1. Flutter 异常概述#
關於 Flutter 異常類型與捕獲的文章網上已經有許多了,本文不再詳細贅述,此處僅做個小結以保證文章的完整性。
Flutter 異常具體可分為以下幾類:
- Dart 異常
- App 異常
- 同步異常
- 異步異常
- Framework 異常
- App 異常
- 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
:該配置對應的宿主 AppminVersion
:最小生效版本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 界限)
- 降級率,灰度率
- ……
最後,結合產物動態加載與降級策略的啟動流程圖如下所示: