Flutter异常和Java异常类似,都是代码运行时发生的错误事件,我们可以通过与Java类似的try-catch机制来捕获这个异常,和java不同的地方在于 Dart 采用事件循环的机制来运行任务,各个任务的运行状态是互相独立的,也就是说,即便某个任务出现了异常我们没有捕获它,Dart 程序也不会退出,只会导致当前任务后续的代码不会被执行,用户仍可以继续使用其他功能。
dart中的异常分为App异常和Framework异常,根据来源不同,捕获方式也不同。App异常我们可以通过try–catch或者异步调用中的catchError捕获;Framework中的异常可以通过自定义ErrorWidget.builder进行捕获+展示
App异常的捕获方式
App异常,就是应用代码的异常,通常由未处理应用层其他模块所抛出的异常引起。根据异常代码的执行时序,App异常可以分为两类,即同步异常和异步异常:同步异常可以通过try-catch机制捕获,异步异常则需要采用Future 提供的catchError语句捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| try{ throw StateError("this is a dart exception");
}catch (e){ print(e); }
Future.delayed(Duration(seconds: 1)) .then((value) => throw StateError("this is a dart exception in future") ) .catchError((e)=>print(e));
try{ Future.delayed(Duration(seconds: 1)) .then((value) => throw StateError("this is a dart exception in future by try catch") ); }catch(e){ print(e); }
|
需要注意的是,这两种方式是不能混用的。无法使用try- -catch去捕获一个异步调用所抛出的异常的。如果我们想集中管理代码中的所有异常,Flutter 也提供了Zone.runZoned 方法。我们可以给代码执行对象指定一-个Zone,在Dart中,Zone表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果我们想要观察沙盒中代码执行出现的异常,沙盒提供了onError回调函数,拦截那些在代码执行对象中的未捕获异常。
在下面的代码中,我们将可能抛出异常的语句放置在了Zone里。可以看到,在没有使用try-catch和catchError的情况下,无论是同步异常还是异步异常,都可以通过Zone直接捕获到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| runZoned(() { throw StateError("this is a dart exception in future"); }, onError: (dynamic e, StackTrace stack) { print(e); print("catch exception by zone"); });
runZoned(() { Future.delayed(Duration(seconds: 1)) .then((value) => throw StateError("this is a dart exception in future by try catch")); }, onError: (dynamic e, StackTrace stack) { print(e); print("catch future exception by zone"); });
|
Framework 异常捕获
Framework异常,就是Flutter框架引发的异常,通常是由应用代码触发了Flutter 框架底层的异常判断引起的。比如,当布局错误时(具体一点就是Text控件的构造方法传入null),Flutter 就会自动展示一个包含错误信息的红色页面,这其实是因为,Flutter 框架在调用build方法构建页面时进行了try- -catch的处理,并提供了一个ErrorWidget,用于在出现异常时进行信息提示:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); }, )
|
这个页面反馈的信息比较丰富,适合开发期定位问题。但我们并不想让用户看到这种错误信息,希望给用户展示一个更加友好的页面。因此,我们通常会重写ErrorWidget.builder方法,返回我们自定义的展示信息,下面我们直接返回了一个居中的Text控件:
1 2 3 4 5 6 7
| ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) { return Scaffold( body: Center( child: Text("${flutterErrorDetails.exception}"), ), ); };
|
异常统一处理
为了集中处理框架异常,Flutter 提供了FlutterError类,这个类的onError属性会在接收到框架异常时执行相应的回调。因此,要实现自定义捕获逻辑,我们只要为它提供一个自定义的错误处理回调即可。
在下面的代码中,我们使用Zone提供的handleUncaughtError语句,将Flutter框架的异常统- -转发到当前的Zone中,这样我们就可以统一使用Zone去处理应用内的所有异常了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void main() { FlutterError.onError = (FlutterErrorDetails details) async { Zone.current.handleUncaughtError(details.exception, details.stack); };
runZoned<Future<Null>>(() async { runApp(MyApp()); }, onError: (error, stackTrace) async { print("-------"); print(error); print(stackTrace); print("-------"); }); }
|
以上