Flutter 中的异常处理
说明
FLutter
中的错误不会导致应用程序奔溃,只会终止执行出错代码之后的逻辑,在导致Widget.build()
返回为null
的错误会导致Widget
构建失败,并返回红底黄字的错误原因Widget
(在 Release 模式则会显示为灰底区域);一般来说,Flutter 中的错误都会被FlutterError.onError
捕获并处理;对于异步方法产生异常等Flutter框架
没有捕获的情况,会交由当前代码所在的Zone
处理(这些异常可以使用runZonedGuarded
捕获并处理)。
为什么 flutter 触发异常的时候不会崩溃?
这个和flutter
的消息循环机制有关,任务分两种,一个是微任务microtask
,一个是事件event
,他们有自己的队列,每个任务是相互独立的,一旦某个任务触发异常,也就是导致这个任务后续代码无法执行,并不会影响其他任务执行
本文基于 Flutter (Channel stable, 2.2.3)
详细说明
Flutter 中的错误处理分为以下几种:
try...catch
对于普通的错误,可以通过try...catch
来捕获:
try {
var list = [1, 2];
var three = list[3];
} on RangeError catch (e) {
print("这里是捕获 RangeError 类型的异常 $e");
} catch (e) {
print("这里是兜底的捕获异常 $e");
} finally {
print("这里是无论如何都会执行的代码");
}
对于,异步异常,可以使用await
等待其执行完毕,将其变为同步任务,否则无法则捕获。
ErrorWidget.builder
当在Widget
构建过程中出现错误,导致build()
方法返回null
,Flutter框架
会调用ErrorWidget.builder
返回一个Widget
替代出错的Widget
。
默认情况下,debug
模式返回的是红底黄字的错误提示,而release
模式返回的是灰色Widget
。
可以在RunApp
方法中替换这个默认的错误界面:
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: BodyWidget(),
),
builder: (context, widget) {
// Widget 在 Build 时出错的话,展示此 Widget,
// 如果不定义的话,debug 下为红底黄字错误信息,release 会显示为灰色布局
// errorDetails 在 release 模式下为空
ErrorWidget.builder = (FlutterErrorDetails errorDetails) {
return MainErrorWidget(widget, errorDetails);
};
return widget ?? Container();
},
));
上述代码中的MainErrorWidget
是一个自定义的展示错误信息的页面。
MainErrorWidget
的一种实现方式class MainErrorWidget extends StatelessWidget { Widget? parentWidget; FlutterErrorDetails errorDetails; MainErrorWidget(this.parentWidget, this.errorDetails); @override Widget build(BuildContext context) { print("3. 布局出现错误,展示错误页面,此处错误在release中也会调用FlutterError.onError"); Widget error = Card( child: SingleChildScrollView( child: Container( padding: const EdgeInsets.all(20), color: Colors.green, child: Text( '布局出现错误,以下是错误信息:\n$errorDetails', style: TextStyle(fontSize: 10, color: Colors.white), ), ), ), ); if (parentWidget is Scaffold || parentWidget is Navigator) { debugPrint( "widget${parentWidget?.key?.toString()} ($parentWidget) is Scaffold ${parentWidget is Scaffold} or Navigator ${parentWidget is Navigator}"); // error = Container(child: error); } return error; } }
需要注意的是,错误Widget
在Debug
和Release
模式下有一些区别:
Debug
模式下ErrorWidget.builder
会返回错误详细信息FlutterErrorDetails
,Release
下则不会;Debug
模式下,Widget
等出错会打印Exception caught by widgets library ...
等提示并输出错误堆栈信息,但是Release
模式下不会;Debug
模式下,出错不会调用FlutterError.onError
,Release
模式下会。
FlutterError.onError
上述几种情况都没有处理的,被 Flutter 框架引起的异常,会在这里被处理。
在Flutter 2.2.3
中,Debug
模式下如onPressed
中的未捕获错误等都会被 Widget 等捕获,而不会走到这里来,在Release
模式下则会调用FlutterError.onError
。
在这里可以对错误进行处理,比如输出到控制台、交给 Zone 统一处理、直接结束掉 APP 等:
FlutterError.dumpErrorToConsole(details);
输出到控制台exit(1);
退出 APPZone.current.handleUncaughtError(details.exception, details.stack);
交给 Zone 统一处理defaultOnError?.call(details);
自己处理完异常后,也要把异常向上抛【推荐】,其中defaultOnError
可以预先缓存final defaultOnError = FlutterError.onError;
runZonedGuarded(onError)
上述几种情况都没有处理的异常,会被发送到这里处理,可以类比为Android
中的Thread.UncaughtExceptionHandler
。
runZonedGuarded(() async {
runApp(...);
},
(Object error, StackTrace stack) {
// 没有被 Flutter 捕获的错误,全局未捕获异常处理,类似于 Android 的 Thread.UncaughtExceptionHandler
/// 比如异步的方法
print("2. runZonedGuarded.onError $error");
});
Zone 可以理解为一个沙盒,其中的代码出错,包括异步的都可以捕获到。但是如果是另外一个沙盒中的错误则无法处理。