Dart代码运行时发生的异常与Java、kotlin、OC等具有多线程模型的编程语言不同,Dart是一门单线程的编程语言,采用事件循环机制来运行任务,所以各个任务的运行状态是互相独立的。也即是说,当程序运行过程中出现异常时,即使没有像Java那样使用try-catch机制来捕获异常,Dart程序也不会退出,只会导致当前任务后续的代码不会被执行,而其它功能仍然可以继续使用。
Dart 提供了 Exception
和Error
类型, 以及一些子类型。你还 可以定义自己的异常类型。但是, Dart 代码可以 抛出任何非 null 对象为异常,不仅仅是实现了 Exception
或者Error
的对象。
throw new Exception('这是一个异常');
throw ArgumentError('Amount must be greater than zero');
throw '这是一个异常';
throw 123;
异常捕获
Dart中捕获异常同样是使用catch
语句,但是Dart中的catch
无法指定异常类型。需要结合on
来使用。
try {
throw 123;
} on int catch(e){
//使用 on 指定捕获int类型的异常对象
} catch(e,s){
//函数 catch() 可以带有一个或者两个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( StackTrace 对象)
rethrow; //使用 `rethrow` 关键字可以 把捕获的异常给 重新抛出
} finally{
}
根据异常代码的执行时序,Dart异常可以分为同步和异步异常两类。首先我们看同步异常的捕获方式:
同步异常的捕获方式:
//使用try-catch捕获同步异常
try {
throw StateError('There is a dart exception.');
} catch (e) {
print(e);
}
异步异常的捕获有几种方式:
1.使用Future提供的catchError语句来进行捕获;
//使用catchError捕获异步异常
Future.delayed(Duration(seconds: 1))
.then(
(e) => throw StateError('This is first Dart exception in Future.'))
.catchError((e) => print(e));
2.将异步转同步然后通过try-catch进行捕获;
try {
await Future.delayed(Duration(seconds: 1)).then(
(e) => throw StateError('This is second Dart exception in Future.'));
} catch (e) {
print(e);
}
3.stream 流错误处理:在处理流时,你可以使用 listen()
方法来分别处理数据和错误
stream.listen(
(data) {
// 处理数据
},
onError: (error) {
// 处理错误
print('Error in stream: $error');
},
onDone: () {
// 流关闭
},
);
集中捕获异常
在Android中我们可以通过Thread.UncaughtExceptionHandler
接口来集中收集异常,那么在Flutter中如何集中收集异常呢?
Flutter提供的Zone.runZonedGuarded()
方法。在Dart语言中,Zone表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果想要处理沙盒中代码执行出现的异常,可以使用沙盒提供的onError回调函数来拦截那些在代码执行过程中未捕获的异常:
runZonedGuarded(() {
throw StateError('runZonedGuarded:This is a Dart exception.');
}, (e, s) => print(e));
runZonedGuarded(() {
Future.delayed(Duration(seconds: 1)).then((e) => throw StateError(
'runZonedGuarded:sThis is a Dart exception in Future.'));
}, (e, s) => print(e));
从上述代码中不难看出,无论是同步异常还是异步异常,都可以使用Zone直接捕获到。同时,如果需要集中捕获Flutter应用中未处理的异常,那么可以把main函数中的runApp语句也放置在Zone中,这样就可以在检测到代码运行异常时对捕获的异常信息进行统一处理:
runZonedGuarded<Future<Null>>(() async {
runApp(BiliApp());
}, (e, s) => print(e));
案例
void main() {
HxhDefend().run();
}
...
import 'dart:async';
///异常统一处理
/**
* 如果你的应用在runApp 中调用了 WidgetsFlutterBinding.ensureInitialized() 方法来进行一些初始化操作,
* 则必须在runZonedGuarded中调用WidgetsFlutterBinding.ensureInitialized()
*/
class HxhDefend {
run() {
//框架异常
FlutterError.onError = (FlutterErrorDetails details) async {
//线上环境,走上报逻辑
if (kReleaseMode) {
Zone.current.handleUncaughtError(details.exception, details.stack!);
} else {
//开发期间,走Console抛出
FlutterError.dumpErrorToConsole(details);
}
};
runZonedGuarded(() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}, (e, s) => _reportError(e, s));
}
///通过接口上报异常
_reportError(Object error, StackTrace s) {
HxhLogUtil.e('kReleaseMode:$kReleaseMode');
HxhLogUtil.e('catch error:$error');
}
}
注:如果你的应用在runApp 中调用了 WidgetsFlutterBinding.ensureInitialized() 方法来进行一些初始化操作,则必须在runZonedGuarded中调用WidgetsFlutterBinding.ensureInitialized()
异常上报
捕获到异常后可以在上述_reportError
方法中上报到服务端,像BAT等一线互联网大厂都有自己的Crash监控平台。如果公司没有自己的Crash平台,可以接入第三方的如:
Buggly
sentry 支持私有化部署,开发版可免费使用。
https://docs.sentry.io/platforms/flutter/
代码集成:
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'https://examplePublicKey@o0.ingest.sentry.io/0';
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = 1.0;
},
appRunner: () => runApp(MyApp()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}