专栏首页flutter开发者​Flutter中异常处理

​Flutter中异常处理

Dart是单进程机制,所以在这个进程中出现问题时仅仅会影响当前进程,在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。

Flutter 异常

Flutter 异常指的是,Flutter 程序中 Dart 代码运行时意外发生的错误事件。我们可以通过与 Java 类似的 try-catch 机制来捕获它。但与 Java 不同的是,Dart 程序不强制要求我们必须处理异常。

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

Dart 异常,根据来源又可以细分为 App 异常和 Framework 异常。Flutter 为这两种异常提供了不同的捕获方式,接下来我们就一起看看吧。

App 异常的捕获方式

App 异常,就是应用代码的异常,通常由未处理应用层其他模块所抛出的异常引起。根据异常代码的执行时序,App 异常可以分为两类,即同步异常和异步异常:同步异常可以通过 try-catch 机制捕获,异步异常则需要采用 Future 提供的 catchError 语句捕获。

这两种异常的捕获方式,如下代码所示:

// 使用 try-catch 捕获同步异常
try {
  throw StateError('This is a Dart exception.');
}
catch(e) {
  print(e);
}
 
// 使用 catchError 捕获异步异常
Future.delayed(Duration(seconds: 1))
    .then((e) => throw StateError('This is a Dart exception in Future.'))
    .catchError((e)=>print(e));
    
// 注意,以下代码无法捕获异步异常
try {
  Future.delayed(Duration(seconds: 1))
      .then((e) => throw StateError('This is a Dart exception in Future.'))
}
catch(e) {
  print("This line will never be executed. ");
}

需要注意的是,这两种方式是不能混用的。可以看到,在上面的代码中,我们是无法使用 try-catch 去捕获一个异步调用所抛出的异常的。

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

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

在下面的代码中,我们将可能抛出异常的语句放置在了 Zone 里。可以看到,在没有使用 try-catch 和 catchError 的情况下,无论是同步异常还是异步异常,都可以通过 Zone 直接捕获到:

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 应用中的未处理异常,可以把 main 函数中的 runApp 语句也放置在 Zone 中。这样在检测到代码中运行异常时,我们就能根据获取到的异常上下文信息,进行统一处理了:

runZoned<Future<Null>>(() async {
  runApp(MyApp());
}, onError: (error, stackTrace) async {
 //Do sth for error
});

下面,我们再看看 Framework 异常应该如何捕获吧。

Flutter 框架异常捕获

Flutter 框架为我们在很多关键的方法进行了异常捕获。这里举一个例子,当我们布局发生越界或不和规范时,Flutter就会自动弹出一个错误界面,这是因为Flutter已经在执行build方法时添加了异常捕获,最终的源码如下:

@override
void performRebuild() {
 ...
  try {
    //执行build方法
    built = build();
  } catch (e, stack) {
    // 有异常时则弹出错误提示
    built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
  }
  ...
}

可以看到,在发生异常时,Flutter 默认的处理方式时弹一个 ErrorWidget ,但如果我们想自己捕获异常并上报到报警平台的话应该怎么做?我们进入 _debugReportException() 方法看看:

FlutterErrorDetails _debugReportException(
  String context,
  dynamic exception,
  StackTrace stack, {
  InformationCollector informationCollector
}) {
  //构建错误详情对象
  final FlutterErrorDetails details = FlutterErrorDetails(
    exception: exception,
    stack: stack,
    library: 'widgets library',
    context: context,
    informationCollector: informationCollector,
  );
  //报告错误
  FlutterError.reportError(details);
  return details;
}

我们发现,错误是通过 FlutterError.reportError 方法上报的,继续跟踪:

static void reportError(FlutterErrorDetails details) {
  ...
  if (onError != null)
    onError(details); //调用了onError回调
}

我们发现 onErrorFlutterError 的一个静态属性,它有一个默认的处理方法dumpErrorToConsole,到这里就清晰了,如果我们想自己上报异常,只需要提供一个自定义的错误处理回调即可,如:

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    reportError(details);
  };
 ...
}

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

FlutterError.onError = (FlutterErrorDetails details) async {
  // 转发异常到 Zone 中
  Zone.current.handleUncaughtError(details.exception, details.stack);
};
 
runZoned<Future<Null>>(() async {
  runApp(MyApp());
}, onError: (error, stackTrace) async {
 //处理所有异常信息
});

同样的我们可以使用ErrorWidget.builder来自定义错误界面

ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) {
  return Scaffold(
      body: Center(
    child: Column(children: [
      Icon(
        Icons.error,
        color: Colors.red,
        size: 100,
      ),
      Text(flutterErrorDetails.exceptionAsString())
    ]),
  ));
};

一个局中显示的错误图片和错误文本

异常处理

在错误界面我们可以根据Zone中的错误回调处理所有捕获的异常,当然,我们可以考虑把 错误文件存储到文件,上传到服务器或者上传到错误分析平台。

import 'dart:async';

import 'package:flutter/material.dart';

main() {
  FlutterError.onError = (FlutterErrorDetails details) async {
    // 转发至 Zone 的错误回调
    Zone.current.handleUncaughtError(details.exception, details.stack);
  };

ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) {
    return Scaffold(
        body: Container(
      padding: EdgeInsets.only(left: 20, right: 20),
      child: Column(children: [
        Icon(
          Icons.error,
          color: Colors.red,
          size: 100,
        ),
        Text(
          flutterErrorDetails.exceptionAsString(),
          style: TextStyle(color: Colors.blue, fontSize: 14),
          textAlign: TextAlign.start,
        )
      ]),
    ));
  };

  runZoned<Future<Null>>(() async {
    runApp(new MaterialApp(
      home: HomePage(),
    ));
  }, onError: (error, stackTrace) async {
    print(error.toString());
  });
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ExceptionTitle"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("异常"),
          onPressed: () {
            throw new Exception("自定义异常");
          },
        ),
      ),
    );
  }
}

我们定义一个界面中间是一个按钮,点击按钮就会抛出一个自定义异常,在main方法中我们将FlutterError中的错误回调到Zone中,并把捕获到的异常信息打印到控制台。

点击异常按钮,观察控制台输出

同样的我们自定义了错误界面,当界面构建发生错误时就会显示我们自定义的错误界面

小结

  • App 异常,我们可以将代码执行块放置到 Zone 中,通过 onError 回调进行统一处理
  • Framework 异常,我们可以使用 FlutterError.onError 回调进行拦截
  • 通过将FlutterError.onError转发到Zone中可以统一进行异常处理
  • ErrorWidget.builder可以自定义错误界面

本文分享自微信公众号 - flutter开发者(Flutter_Developers),作者:Flutter开发者

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Dart中的异步操作

    在前面的文章中我们很多次提到了Future这个东西,这个单词翻译过来的意思是‘未来’的意思。在flutter中它表示一个未来某些时候返回数据的一个对象。

    flyou
  • Flutter动画【1】

    在前面的文章中我们花了很多的时间去讲了Flutter中的Widget以及用户操作,但是我们却很少去关注与用户的交互效果,当然这并不会导致我们的程序崩溃或者不能实...

    flyou
  • Flutter动画【3】

    在前面的文章中我们看了下Flutter中的补间动画和Flutter Widgets,今天我们来看下页面过渡动画,也可以叫做共享元素动画,页面A的元素过渡到页面B...

    flyou
  • python基础学习15----异常处理

    异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。

    py3study
  • Visual Studio 2008 每日提示(二十八)

    #271、启用地址级调试的设置的作用是什么? 原文链接:What the Enable Address-Level Debugging option does...

    Jianbo
  • 异常基础

    Throwable类 Java中所以异常的超类,在Java中所有的异常,错误的基类就是Throwable类。

    用户7073689
  • ML基石_12_NonLinearTransformation

    retro quadratic hypothesis nonlinear transform price on nonlinear transform stru...

    用户1147754
  • 深圳特区40周年,听说90%的深圳人都在做这件事

    ? 号外!号外! 今天深圳90%的人都在做这件事 还不快来围观? ? 只需上传1张正面照 即可获得8个以自己为主角的动漫视频 ? 扫描下图二维码 或者点击下方...

    腾讯云AI中心
  • Java 异常处理的误区和经验总结

    本文着重介绍了 Java 异常选择和使用中的一些误区,希望各位读者能够熟练掌握异常处理的一些注意点和原则,注意总结和归纳。只有处理好了异常,才能提升开发人员的基...

    Java后端工程师
  • Java异常处理的误区和经验总结

    三哥

扫码关注云+社区

领取腾讯云代金券