核心类图
典型使用例子
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按钮响应。
构造函数:
构造函数传递了如下参数:
- rootWidget,也就是异常监听的根节点,一般是App的顶级Widget。
- releaseConfig,debugConfig和profileConfig:均为可选,若没有则异常不会处理。对应release,debug和profile三种模式下的异常捕获配置。
- enableLogger:是否开启日志记录,默认为true。
- navigatorKey:用于打开弹层或新页面的全局状态key。可以外部指定,如果没指定则会在Catcer内部创建一个以便通过GlobalKey获取BuildContext。
- 构造函数调用了一个)_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都是抽象类,因此可以自定义实现类,自己实现异常的报告形式和处理方式。