Flutter异常监控 - 壹 | 从Zone说起
  QeCuIDOH0qiv 2023年11月02日 31 0

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。

❤️ 本文原创​听蝉​ 公众号:码里特别有禅 欢迎关注原创技术文章第一时间推送 ❤️

如果你正需要处理Flutter异常捕获,那么恭喜你,找对地了,这里从根源上给你准备了Flutter异常捕获需要是所有知识和原理,让你更深刻认识Flutter Zone概念。

Zone是什么

/// A zone represents an environment that remains stable across asynchronous
/// calls.

SDK中描述:表示一个环境,这个环境为了保持稳定异步调用。

通俗理解39 | 线上出现问题,该如何做好异常捕获与信息采集?中描述:

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

Zone创建

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的一些配置,可以自定义一些代码行为,比如拦截日志输出和错误等

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. Zone回调收拢了异步捕获入口,提高了可维护性。
  2. 未预料的未捕获异常可以帮你自动捕获到,提高便捷性。

是不是所有异常都可以捕获到?

不是, 只能处理情况1。

  1. Zone默认捕获范围主要针对异步异常或者一般逻辑异常等常规异常,比如Future中出了问题,或者逻辑处理了1/0,(见Tag3),捕获异步异常原理见简话-Flutter异常处理 - 掘金
  2. Dart中另外比较容易出现的异常是framework异常,比如build异常等,这种异常Zone无法捕获到,原因可以参看Flutter异常捕获和Crash崩溃日志收集 。如果想Zone来处理可这样抛给它(见Tag1)
  3. Flutter Engine和Native异常,isolate异常 不是runZonedGuarded和FlutterError.onError 能处理范围。
  4. isolate异常处理(见Tag2)
    原理参考特别放送 | 温故而知新,与你说说专栏的那些思考题

并发 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方法。详细见Dart中Future、Zone、Timer的源码学习

每次代码进入或退出区域时执行一个操作

例如启动或停止计时器,或保存堆栈跟踪。

如下例子,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

将数据(称为 Zone本地值)与各个其他Zone相关联

这个作用类似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属性被替换掉。

​原文地址​

参考链接

简话-Flutter异常处理 - 掘金

Zones | Dart

Brian Ford - Zones - NG-Conf 2014 - YouTube

[Flutter] 认识Zone和异常处理 - 掘金

2.8 Flutter异常捕获 | 《Flutter实战·第二版》

特别放送 | 温故而知新,与你说说专栏的那些思考题


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  45VoiS2mj7n5   2023年11月02日   20   0   0 ideTextflutter二级Dart
  b1UHV4WKBb2S   2023年11月13日   12   0   0 flutterDart
QeCuIDOH0qiv