如果你正需要处理Flutter异常捕获,那么恭喜你,找对地了,这里从根源上给你准备了Flutter异常捕获需要是所有知识和原理,让你更深刻认识Flutter Zone概念。
/// A zone represents an environment that remains stable across asynchronous /// calls.
SDK中描述:表示一个环境,这个环境为了保持稳定异步调用。
我们可以给代码执行对象指定一个 Zone,在 Dart 中,Zone 表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果我们想要观察沙盒中代码执行出现的异常,沙盒提供了 onError 回调函数,拦截那些在代码执行对象中的未捕获异常。
Dart提供了runZoned方法,支持Zone的快速创建
R runZoned<R>(R body(), {Map<Object?, Object?>? zoneValues, ZoneSpecification? zoneSpecification, @Deprecated("Use runZonedGuarded instead") Function? onError}) {
zoneValues
: Zone 的私有数据,可以通过实例zone[key]
获取,可以理解为每个“沙箱”的私有数据。zoneSpecification
:Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出和错误等import 'dart:async'; //OUTPUT:Uncaught error: Would normally kill the program void main() { runZonedGuarded(() { Timer.run(() { throw 'Would normally kill the program'; }); }, (error, stackTrace) { print('Uncaught error: $error'); }); }
用try catch一样可以捕获,为啥要通过Zone来捕获?
是不是所有异常都可以捕获到?
不是, 只能处理情况1。
并发 Isolate 的异常是无法通过 try-catch 来捕获的。并发 Isolate 与主 Isolate 通信是采用 SendPort 的消息机制,而异常本质上也可以视作一种消息传递机制。所以,如果主 Isolate 想要捕获并发 Isolate 中的异常消息,可以给并发 Isolate 传入 SendPort。而创建 Isolate 的函数 spawn 中就恰好有一个类型为 SendPort 的 onError 参数,因此并发 Isolate 可以通过往这个参数里发送消息,实现异常通知。
完整Dart异常捕获代码
void main() { FlutterError.onError = (FlutterErrorDetails details) { Zone.current.handleUncaughtError(details.exception, details.stack);//Tag1 //或customerReport(details); }; //Tag2 Isolate.current.addErrorListener( RawReceivePort((dynamic pair) async { final isolateError = pair as List<dynamic>; customerReport(details); }).sendPort, ); runZoned( () => runApp(MyApp()), zoneSpecification: ZoneSpecification( print: (Zone self, ZoneDelegate parent, Zone zone, String line) { report(line) }, ), onError: (Object obj, StackTrace stack) { //Tag3 customerReport(e, stack); } ); }
例如print()
和scheduleMicrotask()
main() { runZoned(() { print("test"); }, zoneSpecification: ZoneSpecification( print: (self, parent, zone, s) { parent.print(zone, "hook it: $s"); } )); } //OUTPUT:hook it: test
上面实现的原理是什么呢?
简单讲就是runZoned从root Zone fork了一个子Zone,print打印时如果当前Zone
不为空则使用当前Zone的print来打印,而不使用root Zone的print方法。
例如启动或停止计时器,或保存堆栈跟踪。
如下例子,Zone提供了一个hook点,在执行其中方法时候,可以做额外包装操作(Tag1,Tag2),比如耗时方法打印,这样在不破坏原有代码基础上实现了无侵入的统一逻辑注入。
import 'dart:async'; final total = new Stopwatch(); final user = new Stopwatch(); final specification = ZoneSpecification(run: <R>(self, parent, zone, f) { //Tag1 user.start(); try { return parent.run(zone, f); } finally { //Tag2 user.stop(); } }); void main() { runZoned(() { total.start(); a(); b(); c().then((_) { print(total.elapsedMilliseconds); print(user.elapsedMilliseconds); }); }, zoneSpecification: specification); } void a() { print('a'); } void b() { print('b'); } Future<void> c() { return Future.delayed(Duration(seconds: 5), () => print('c')); }
输出:
a
b
c
5005
6
这个作用类似java中的threadlocal,每个Zone相当于有自己值的作用范围,Zone直接值的传递和共享通过zonevalue来实现。
import 'dart:async'; void main() { Zone firstZone = Zone.current.fork(zoneValues: {"name": "bob"}); Zone secondZone = firstZone.fork(zoneValues: {"extra_values": 12345}); secondZone.run(() { print(secondZone["name"]); // bob print(secondZone["extra_values"]); // 12345 }); }
案例说明:
和Linux类似地,当Zone做Fork的时候,会将父Zone所持有的ZoneSpecification、ZoneValues会继承下来,可以直接使用。并且是支持追加的,secondZone在firstZone的基础之上,又追加了
extra_values
属性,不会因为secondZone的ZoneValues就导致name属性被替换掉。