技术实践第二期|Flutter异常捕获
簡介:應(yīng)用性能穩(wěn)定是良好用戶體驗中非常關(guān)鍵的一環(huán),為了更好保障應(yīng)用性能穩(wěn)定,異常捕獲在保證線上產(chǎn)品穩(wěn)定中扮演著至關(guān)重要的角色。我們團隊在推出了U-APM移動應(yīng)用性能監(jiān)控的產(chǎn)品后,幫助開發(fā)者定位并解決掉很多線上的疑難雜癥。隨著使用人數(shù)的增多,關(guān)注度的提高,在拜訪客戶和開發(fā)者的留言中,很多開發(fā)者都提出希望該產(chǎn)品可以支持flutter框架的異常捕獲。本身我并沒有做過flutter開發(fā),所以主要是通過在現(xiàn)有產(chǎn)品能力基礎(chǔ)上做插件實現(xiàn)異常的上報,這篇文章就記錄我學(xué)習(xí)flutter錯誤處理的過程和遇到的問題。
作者:友盟+技術(shù)專家 彥克
一、背景
應(yīng)用性能穩(wěn)定是良好用戶體驗中非常關(guān)鍵的一環(huán),為了更好保障應(yīng)用性能穩(wěn)定,異常捕獲在保證線上產(chǎn)品穩(wěn)定中扮演著至關(guān)重要的角色。我們團隊在推出了U-APM移動應(yīng)用性能監(jiān)控的產(chǎn)品后,幫助開發(fā)者定位并解決掉很多線上的疑難雜癥。隨著使用人數(shù)的增多,關(guān)注度的提高,在拜訪客戶和開發(fā)者的留言中,很多開發(fā)者都提出希望該產(chǎn)品可以支持flutter框架的異常捕獲。本身我并沒有做過flutter開發(fā),所以主要是通過在現(xiàn)有產(chǎn)品能力基礎(chǔ)上做插件實現(xiàn)異常的上報,這篇文章就記錄我學(xué)習(xí)flutter錯誤處理的過程和遇到的問題。
二、Flutter異常
Flutter 異常指的是,Flutter 程序中 Dart 代碼運行時意外發(fā)生的錯誤事件。
三、Flutter異常特點
Dart是單進程機制,所以在這個進程中出現(xiàn)問題時僅僅會影響當前進程,Dart 采用事件循環(huán)的機制來運行任務(wù),當某個任務(wù)發(fā)生異常并沒有被捕獲時,程序并不會退出,而直接導(dǎo)致的結(jié)果是當前任務(wù)的后續(xù)代碼就不會被執(zhí)行了,也就是說一個任務(wù)中的異常是不會影響其它任務(wù)執(zhí)行的,各個任務(wù)的運行狀態(tài)是互相獨立的。
如:我們可以通過與 Java 類似的 try-catch 機制來捕獲它。但與 Java 不同的是,Dart 程序不強制要求我們必須處理異常。
四、Flutter異常分類
在Flutter開發(fā)中,根據(jù)異常來源的不同,可以將異常分為Framework異常和App異常。Flutter對這兩種異常提供了不同的捕獲方式,Framework異常是由Flutter框架引發(fā)的異常,通常是由于錯誤的應(yīng)用代碼造成Flutter框架底層的異常判斷引起的。而對于App異常,就是應(yīng)用代碼的異常,通常由未處理應(yīng)用層其他模塊所拋出的異常引起。根據(jù)異常代碼的執(zhí)行時序,App 異常可以分為兩類,即同步異常和異步異常。
五、捕獲方式
1.App 異常的捕獲方式
捕獲同步異常使用try-catch 機制:
// 使用 try-catch 捕獲同步異常
try {
?throw StateError('This is a Dart exception.');
}
catch(e) {
?print(e);
}
捕獲異步異常使用Future 提供的 catchError 語句:
// 使用 catchError 捕獲異步異常
Future.delayed(Duration(seconds: 1))
? ?.then((e) => throw StateError('This is a Dart exception in Future.'))
? ?.catchError((e)=>print(e));
看到這里估計很多人心里會問,就不能有一種方式既可以監(jiān)控同步又可以監(jiān)控異步異常嗎?
答案是有的。
Flutter 提供了 Zone.runZoned 方法來管理代碼中的所有異常。我們可以給代碼執(zhí)行對象指定一個Zone,在 Dart 中,Zone 表示一個代碼執(zhí)行的環(huán)境范圍,其概念類似沙盒,不同沙盒之間是互相隔離的。如果我們想要觀察沙盒中代碼執(zhí)行出現(xiàn)的異常,沙盒提供了 onError 回調(diào)函數(shù),攔截那些在代碼執(zhí)行對象中的未捕獲異常。廢話不多說,
Show me the code!
runZoned(() {
?// 同步異常
?throw StateError('This is a Dart exception.');
}, onError: (dynamic e, StackTrace stack) {
?print('Sync error caught by zone');
});
runZoned(() {
?// 異步異常
?Future.delayed(Duration(seconds: 1))
? ? ?.then((e) => throw StateError('This is a Dart exception in Future.'));
}, onError: (dynamic e, StackTrace stack) {
?print('Async error aught by zone');
});
為了能夠集中捕獲 Flutter 應(yīng)用中的未處理異常,最終我把main函數(shù)中的 runApp 語句也放置在 Zone 中。這樣在檢測到代碼中運行異常時,就能根據(jù)獲取到的異常上下文信息,進行統(tǒng)一處理了:
runZoned>(() async {
?runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
});
2.Framework異常捕獲方式
Flutter 框架為我們在很多關(guān)鍵的方法進行了異常捕獲。如果我們想自己上報異常,只需要提供一個自定義的錯誤處理回調(diào)即可,如:
void main() {
?FlutterError.onError = (FlutterErrorDetails details) {
? ?reportError(details);
?};
...
}
有沒有一套從天而降的代碼,能夠統(tǒng)一處理以上異常呢?
3.總結(jié)(一套代碼捕獲所有異常)
runZonedGuarded(() async {
? ?WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (FlutterErrorDetails details) {
? ? ?myErrorsHandler.onError(details.exception,details.stack);
? ?};
? ?runApp(MyApp());
?}, (Object error, StackTrace stack) {
? ?myErrorsHandler.onError(error, stack);
?});
代碼中出現(xiàn)了一句,上訴從沒有出現(xiàn)過的代碼即WidgetsFlutterBinding.ensureInitialized(),當我把這行代碼注釋掉的時候,框架異常是捕獲不到的。
當時困擾了好久最后終于查到了原因:
上圖是Flutter的架構(gòu)層,WidgetFlutterBinding用于與 Flutter 引擎交互。 我們的APM產(chǎn)品需要調(diào)用 native 代碼來初始化,并且由于插件需要使用平臺 channel 來調(diào)用 native 代碼,這是異步完成的,因此必須調(diào)用ensureInitialized()確保你有一個 WidgetsBinding 的實例.
來自 docs :
Returns an instance of the WidgetsBinding, creating and initializing it if necessary. If one is created, it will be a WidgetsFlutterBinding. If one was previously initialized, then it will at least implement WidgetsBinding.
注:如果你的應(yīng)用在runApp 中調(diào)用了 WidgetsFlutterBinding.ensureInitialized() 方法來進行一些初始化操作,則必須在runZonedGuarded中調(diào)用WidgetsFlutterBinding.ensureInitialized()
六、異常上報
異常上報的整體方案是通過已有的插件增加接口,橋接Android APM 和 iOS APM庫的自定義異常上報接口。
插件增加函數(shù)
static void postException(error, stack) {
? ?List args = [error,stack];
? ?//將異常和堆棧上報至umapm
? ?_channel.invokeMethod("postException",args);
?}
Android 端調(diào)用自定義異常上報:
private void postException(List args){
? ?String error = (String)args.get(0);
? ?String stack = (String)args.get(1);
? ?UMCrash.generateCustomLog(stack,error);
?}
iOS端調(diào)用自定義異常上報:
if ([@"postException" isEqualToString:call.method]){
? ? ? ?NSString* error = arguments[0];
? ? ? ?NSString* stack = arguments[1];
? ? ? ?[UMCrash reportExceptionWithName:@"Flutter" reason:error stackTrace:stack terminateProgram:NO];
}
以上就是本期干貨內(nèi)容的介紹,希望我們的技術(shù)內(nèi)容可以更好地幫助開發(fā)者們解決問題,我們將陪伴開發(fā)者們一起進步,一起成長。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的技术实践第二期|Flutter异常捕获的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【视频特辑】提效神器,如何用Quick
- 下一篇: ubuntu16.04下安装opencv