Flutter異常捕獲
Dart中可以通過try/catch/finally來捕獲代碼塊異常顷歌,這個和其它變成語言類似气忠,邻储,如果讀者不清楚,可以查看Dart語言文檔旧噪,不在贅述吨娜,下面我們看看Flutter中的異常捕獲。
Flutter框架異常捕獲
Flutter 框架為我們在很多關鍵的方法進行了異常捕獲淘钟。這里舉一個例子宦赠,當我們布局發(fā)生越界或不和規(guī)范時,F(xiàn)lutter就會自動彈出一個錯誤界面米母,這是因為Flutter已經(jīng)在執(zhí)行build方法時添加了異常捕獲勾扭,最終的源碼如下:
@override
void performRebuild() {
...
try {
//執(zhí)行build方法
built = build();
} catch (e, stack) {
// 有異常時則彈出錯誤提示
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
}
...
}
可以看到,在發(fā)生異常時铁瞒,F(xiàn)lutter默認的處理方式時彈一個ErrorWidget妙色,但如果我們想自己捕獲異常并上報到報警平臺的話應該怎么做?我們進入_debugReportException()方法看看:
FlutterErrorDetails _debugReportException(
String context,
dynamic exception,
StackTrace stack, {
InformationCollector informationCollector
}) {
//構(gòu)建錯誤詳情對象
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: context,
informationCollector: informationCollector,
);
//報告錯誤
FlutterError.reportError(details);
return details;
}
我們發(fā)現(xiàn)慧耍,錯誤是通過FlutterError.reportError方法上報的身辨,繼續(xù)跟蹤:
static void reportError(FlutterErrorDetails details) {
...
if (onError != null)
onError(details); //調(diào)用了onError回調(diào)
}
我們發(fā)現(xiàn)onError是FlutterError的一個靜態(tài)屬性,它有一個默認的處理方法 dumpErrorToConsole芍碧,到這里就清晰了煌珊,如果我們想自己上報異常,只需要提供一個自定義的錯誤處理回調(diào)即可泌豆,如:
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details);
};
...
}
這樣我們就可以處理那些Flutter為我們捕獲的異常了定庵,接下來我們看看如何捕獲其它異常。
其它異常捕獲與日志收集
在Flutter中,還有一些Flutter沒有為我們捕獲的異常洗贰,如調(diào)用空對象方法異常、Future中的異常陨倡。在Dart中敛滋,異常分兩類:同步異常和異步異常,同步異承烁铮可以通過try/catch捕獲绎晃,而異步異常則比較麻煩,如下面的代碼是捕獲不了Future的異常的:
try{
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
print(e)
}
Dart中有一個runZoned(...) 方法杂曲,可以給執(zhí)行對象指定一個Zone庶艾。Zone表示一個代碼執(zhí)行的環(huán)境范圍,為了方便理解擎勘,讀者可以將Zone類比為一個代碼執(zhí)行沙箱咱揍,不同沙箱的之間是隔離的,沙箱可以捕獲棚饵、攔截或修改一些代碼行為煤裙,如Zone中可以捕獲日志輸出、Timer創(chuàng)建噪漾、微任務調(diào)度的行為硼砰,同時Zone也可以捕獲所有未處理的異常。下面我們看看runZoned(...)方法定義:
R runZoned<R>(R body(), {
Map zoneValues,
ZoneSpecification zoneSpecification,
Function onError,
})
zoneValues: Zone 的私有數(shù)據(jù)欣硼,可以通過實例zone[key]獲取题翰,可以理解為每個“沙箱”的私有數(shù)據(jù)。
zoneSpecification:Zone的一些配置诈胜,可以自定義一些代碼行為豹障,比如攔截日志輸出行為等,舉個例子:
下面面是攔截應用中所有調(diào)用print輸出日志的行為耘斩。
main() {
runZoned(() => runApp(MyApp()), zoneSpecification: new ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
parent.print(zone, "Intercepted: $line");
}),
);
}
這樣一來沼填,我們APP中所有調(diào)用print方法輸出日志的行為都會被攔截,通過這種方式括授,我們也可以在應用中記錄日志坞笙,等到應用觸發(fā)未捕獲的異常時,將異常信息和日志統(tǒng)一上報荚虚。ZoneSpecification還可以自定義一些其他行為薛夜,讀者可以查看API文檔。
onError:Zone中未捕獲異常處理回調(diào)版述,如果開發(fā)者提供了onError回調(diào)或者通過ZoneSpecification.handleUncaughtError指定了錯誤處理回調(diào)梯澜,那么這個zone將會變成一個error-zone,該error-zone中發(fā)生未捕獲異常(無論同步還是異步)時都會調(diào)用開發(fā)者提供的回調(diào)渴析,如:
runZoned(() {
runApp(MyApp());
}, onError: (Object obj, StackTrace stack) {
var details=makeDetails(obj,stack);
reportError(details);
});
這樣一來晚伙,結(jié)合上面的FlutterError.onError我們就可以捕獲我們Flutter應用中全部錯誤了吮龄!需要注意的是,error-zone內(nèi)部發(fā)生的錯誤是不會跨越當前error-zone的邊界的咆疗,如果想跨越error-zone邊界去捕獲異常漓帚,可以通過共同的“源”zone來捕獲,如:
var future = new Future.value(499);
runZoned(() {
var future2 = future.then((_) { throw "error in first error-zone"; });
runZoned(() {
var future3 = future2.catchError((e) { print("Never reached!"); });
}, onError: (e) { print("unused error handler"); });
}, onError: (e) { print("catches error of first error-zone."); }
);
總結(jié)
我們最終的異常捕獲和上報代碼如下:
void collectLog(String line){
... //收集日志
}
void reportErrorAndLog(FlutterErrorDetails details){
... //上報錯誤和日志邏輯
}
FlutterErrorDetails makeDetails(Object obj, StackTrace stack){
...// 構(gòu)建錯誤信息
}
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportErrorAndLog(details);
};
runZoned(
() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
collectLog(line); //手機日志
},
),
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
reportErrorAndLog(details);
},
);
}