1. Flutter 异常

Flutter 异常指的是 Dart 代码运行时的错误事件,但与 Java 不同的是,Dart 不强制要求我们必须处理异常。这是因为,Dart 采用事件循环的机制来运行任务,所以各个任务的运行状态是互相独立的。也就是说,即便某个任务出现了异常我们没有捕获它,Dart 程序也不会退出,只会导致当前任务后续的代码不会被执行,用户仍可以继续使用其他功能。

Dart 异常,根据来源可以分为以下两种:

  1. App 异常
  2. Framework 异常

1.1 App 异常的捕获方式

App 异常可以分为两类,即同步异常和异步异常:

  1. 同步异常可以通过 try-catch 机制捕获
  2. 异步异常则需要采用 Future 提供的 catchError 语句捕获。

需要注意的是,这两种方式是不能混用的。

同步的 try-catch 和异步的 catchError,为我们提供了直接捕获特定异常的能力,而如果我们想集中管理代码中的所有异常,Flutter 也提供了 Zone.runZoned 方法。

我们可以给代码执行对象指定一个 Zone,在 Dart 中,Zone 表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果我们想要观察沙盒中代码执行出现的异常,沙盒提供了 onError 回调函数,拦截那些在代码执行对象中的未捕获异常。

因此,如果我们想要集中捕获 Flutter 应用中的未处理异常,可以把 main 函数中的 runApp 语句也放置在 Zone 中。这样在检测到代码中运行异常时,我们就能根据获取到的异常上下文信息,进行统一处理了:

  1. runZoned>(() async {
  2. runApp(MyApp());
  3. }, onError: (error, stackTrace) async {
  4. //Do sth for error
  5. }
  6. );


1.2 Framework 异常捕获

Framework 异常,就是 Flutter 框架引发的异常,通常是由应用代码触发了 Flutter 框架底层的异常判断引起的。比如,当布局不合规范时,Flutter 就会自动弹出一个触目惊心的红色错误界面。这其实是因为,Flutter 框架在调用 build 方法构建页面时进行了 try-catch 的处理,并提供了一个 ErrorWidget,用于在出现异常时进行信息提示。

  1. @override
  2. void performRebuild() {
  3. Widget built;
  4. try {
  5. //创建页面
  6. built = build();
  7. } catch (e, stack) {
  8. //使用ErrorWidget创建页面
  9. built = ErrorWidget.builder(_debugReportException(ErrorDescription("building $this"), e, stack));
  10. ...
  11. }
  12. ...
  13. }

这个页面反馈的信息比较丰富,适合开发期定位问题。但如果让用户看到这样一个页面,就很糟糕了。因此,我们通常会重写 ErrorWidget.builder 方法,将这样的错误提示页面替换成一个更加友好的页面。下面的代码演示了自定义错误页面的具体方法。在这个例子中,我们直接返回了一个居中的 Text 控件:

  1. ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
  2. return Scaffold(
  3. body: Center(
  4. child: Text("Custom Error Widget"),
  5. )
  6. );
  7. };

为了集中处理框架异常,Flutter 提供了 FlutterError 类,这个类的 onError 属性会在接收到框架异常时执行相应的回调。因此,要实现自定义捕获逻辑,我们只要为它提供一个自定义的错误处理回调即可。

在下面的代码中,我们使用 Zone 提供的 handleUncaughtError 语句,将 Flutter 框架的异常统一转发到当前的 Zone 中,这样我们就可以统一使用 Zone 去处理应用内的所有异常了:

  1. FlutterError.onError = (FlutterErrorDetails details) async {
  2. //转发至Zone中
  3. Zone.current.handleUncaughtError(details.exception, details.stack);
  4. };
  5. runZoned<Future<Null>>(() async {
  6. runApp(MyApp());
  7. }, onError: (error, stackTrace) async {
  8. //Do sth for error
  9. });

2. 异常上报

关于开发者数据上报,目前市面上有很多优秀的第三方 SDK 服务厂商,比如友盟、Bugly,以及开源的 Sentry 等,而它们提供的功能和接入流程都是类似的。