Flutter统一异常捕获组件Catcher源码解析

核心类图

Catcher核心类图

典型使用例子

GlobalExceptionHandler为自定义的全局异常捕获处理代码

CatcherOptions debugOptions = CatcherOptions(PageReportMode(), [
    ConsoleHandler(enableDeviceParameters: true), GlobalExceptionHandler()
  ]);

  CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [
    ToastHandler()
  ]);

  var navigatorKey = GlobalKey();

  Catcher(MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, navigatorKey: navigatorKey);

核心类Catcher

用于配置整个统一异常捕获。通过mixin的方式实现了抽象类ReportModeAction的两个方法:

  • onActionConfirmed(Report report)
  • onActionRejected(Report report)

对应的其实是通过DialogReportMode或PageReportMode中的点击Accept和Cancel按钮响应。

构造函数:

构造函数传递了如下参数:

  1. rootWidget,也就是异常监听的根节点,一般是App的顶级Widget。
  2. releaseConfig,debugConfig和profileConfig:均为可选,若没有则异常不会处理。对应release,debug和profile三种模式下的异常捕获配置。
  3. enableLogger:是否开启日志记录,默认为true。
  4. navigatorKey:用于打开弹层或新页面的全局状态key。可以外部指定,如果没指定则会在Catcer内部创建一个以便通过GlobalKey获取BuildContext。
  5. 构造函数调用了一个)_configure方法,在_configure方法中通过调用一系列方法将整个异常捕获配置完成。代码如下:

  void _configure(GlobalKey navigatorKey) {
    _instance = this;  //_instance指向自身,方便外部访问
    //配置navigationKey,如果外部传入则使用外部,否则自己创建一个
    _configureNavigatorKey(navigatorKey); 
    //配置Logger,如果启用了Logger则会在监听回调中打印错误日志
    _configureLogger();
    //根据当前的开发环境参数配置选择当前的config(release,debug或profile)
    _setupCurrentConfig();
    //设置错误钩子函数,即FlutterError.onError方法。
    _setupErrorHooks();
    //设置当前配置reportMode的响应对象为当前实例
    _setupReportModeActionInReportMode();

    //加载设备信息
    _loadDeviceInfo();
    //加载应用信息
    _loadApplicationInfo();

    //至少需要一个异常处理对象
    if (_currentConfig.handlers.isEmpty) {
      _logger
          .warning("Handlers list is empty. Configure at least one handler to "
              "process error reports.");
    } else {
      _logger.fine("Catcher configured successfully.");
    }
  }

关键方法

关键方法有两个,分别是:** _setupErrorHooks**:设置全局的错误处理,并通过runZoned沙箱的方式运行应用。

 Future _setupErrorHooks() async {
    FlutterError.onError = (FlutterErrorDetails details) async {
      _reportError(details.exception, details.stack, errorDetails: details);
    };

    ///Web doesn't have Isolate error listener support
    if (!ApplicationProfileManager.isWeb()) {
      Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
        var isolateError = pair as List;
        _reportError(
          isolateError.first.toString(),
          isolateError.last.toString(),
        );
      }).sendPort);
    }

    runZoned(() async {
      runApp(rootWidget);
    }, onError: (error, stackTrace) async {
      _reportError(error, stackTrace);
    });
  }

首先是指定了FlutterError.onError方法,FlutterError.onError是全局的错误处理方法,定义如下:

/// Called whenever the Flutter framework catches an error.
  ///
  /// The default behavior is to call [dumpErrorToConsole].
  ///
  /// You can set this to your own function to override this default behavior.
  /// For example, you could report all errors to your server.
  ///
  /// If the error handler throws an exception, it will not be caught by the
  /// Flutter framework.
  ///
  /// Set this to null to silently catch and ignore errors. This is not
  /// recommended.
  static FlutterExceptionHandler onError = dumpErrorToConsole;

FlutterExceptionHandler是一个函数别名,定义如下:

typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);

通过注释可以看到,FlutterError.onError在Flutter框架捕获到错误时会调用,如果不设置的话框架会忽略错误(不推荐),因此可以通过自定义该方法的实现处理错误。因此在_setupErrorHooks首先做了全局错误处理。FlutterErrorDetails定义了错误信息,主要包括exception和stack。Isolate是dart 2.6引入的,其实就是类似苹果的沙箱机制执行的上下文,这个只在移动端支持。允许在同一个沙箱中运行的代码共享存储空间。而不同的沙箱之间需要通过端口进行通信(SendPort和ReceivePort)。通过addErrorListener构建沙箱错误监听。通过addErrorListener注释可以知道,SendPort具有如下特性:

  • 沙箱未捕获的错误会传递到port
  • 错误封装在一个只有两个元素的list中,第一个是错误的文本描述,通常通过toString方法获得错误描述;第二个是错误栈(stack trace)的文本描述,也可能是null如果没有错误栈信息。
  • 重复利用port没有意义,因为只能接收一次错误。
  • 关闭接收端口(Receive Port)并不会阻止Send Port继续发送错误。如果要停止发送,应该使用removeErrorListener.
  • 由于沙箱是并行运行的,有可能在建立listener前,沙箱应用退出。因此,应该启动暂停的沙箱,添加listener然后再继续沙箱运行。

因此,给Isolate.current添加了一个错误监听器,通过RawReiceivePort对象接收错误,并通过RawReceivePort的sendPort配对发送错误。之后是runZoned方法以沙箱机制运行应用,并指定了onError处理错误方法。
所有的错误都是通过_report方法处理的,_report方法是将错误信息封装了一个Report对象(包括时间,设备信息,应用信息,错误信息,错误栈)。然后检查当前错误是否有指定异常处理模式(ReportMode,抽象类),如有有则使用指定错误模式处理,否则使用当前配置currentConfig的异常处理进行处理。错误处理是通过reportMode.requestAction进行处理的。requestAction则是在ReportMode的实现类中实现的。而具体的设定则是在CatcherOptions的构造方法中设置的具体的RepotMode的实现类,诸如DialogReportMode,PageReportMode等等。
_setupReportModeActionInReportMode:这个方法设置的ReportMode的reportModeAction属性,其实就是绑定了对应DialogReportMode和PageReportMode点击Accept和Cancel按钮后的响应方法到Catcher中。具体可以看一下PageReportMode的实现就知道了。因此,如果自定义ReportMode是完全可以的,只需要继承ReportMode,并实现对应的方法即可。通过自定义错误ReportMode可以做很多业务处理,如网络接口错误数据可以统一抛出异常,然后进行相应业务处理。

总结

从上述的分析来看,实际异常处理的分工如下:

  • ReportMode:定义错误的报告形式,如控制台打印,对话框,新页面等等
  • ReportHandler:确定错误的具体方式:在ReportMode点击Accept后会依次调用CatherOptions中的reportHandler处理异常。
  • ReportMode和ReportHandler都是抽象类,因此可以自定义实现类,自己实现异常的报告形式和处理方式。

你可能感兴趣的:(Flutter统一异常捕获组件Catcher源码解析)