【Flutter实战】Dart线程模型及异常捕获

浩浩 等级 369 0 0

2.6 Flutter异常捕获

在介绍Flutter异常捕获之前必须先了解一下Dart单线程模型,只有了解了Dart的代码执行流程,我们才能知道该在什么地方去捕获异常。

2.6.1 Dart单线程模型

在Java和Objective-C(以下简称“OC”)中,如果程序发生异常且没有被捕获,那么程序将会终止,但是这在Dart或JavaScript中则不会!究其原因,这和它们的运行机制有关系。Java和OC都是多线程模型的编程语言,任意一个线程触发异常且该异常未被捕获时,就会导致整个进程退出。但Dart和JavaScript不会,它们都是单线程模型,运行机制很相似(但有区别),下面我们通过Dart官方提供的一张图来看看Dart大致运行原理:

【Flutter实战】Dart线程模型及异常捕获

Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。从图中可以发现,微任务队列的执行优先级高于事件队列。

现在我们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件任务执行完毕后程序便会退出,但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而Flutter中,主线程的执行过程正是如此,永不终止。

在Dart中,所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。值得注意的是,我们可以通过Future.microtask(…)方法向微任务队列插入一个任务。

在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。

2.6.2 Flutter异常捕获

Dart中可以通过try/catch/finally来捕获代码块异常,这个和其它编程语言类似,如果读者不清楚,可以查看Dart语言文档,不再赘述,下面我们看看Flutter中的异常捕获。

Flutter框架异常捕获

Flutter 框架为我们在很多关键的方法进行了异常捕获。这里举一个例子,当我们布局发生越界或不合规范时,Flutter就会自动弹出一个错误界面,这是因为Flutter已经在执行build方法时添加了异常捕获,最终的源码如下:

@override
void performRebuild() {
 ...
  try {
    //执行build方法  
    built = build();
  } catch (e, stack) {
    // 有异常时则弹出错误提示  
    built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
  } 
  ...
}      

可以看到,在发生异常时,Flutter默认的处理方式是弹一个ErrorWidget,但如果我们想自己捕获异常并上报到报警平台的话应该怎么做?我们进入_debugReportException()方法看看:

FlutterErrorDetails _debugReportException(
  String context,
  dynamic exception,
  StackTrace stack, {
  InformationCollector informationCollector
}) {
  //构建错误详情对象  
  final FlutterErrorDetails details = FlutterErrorDetails(
    exception: exception,
    stack: stack,
    library: 'widgets library',
    context: context,
    informationCollector: informationCollector,
  );
  //报告错误 
  FlutterError.reportError(details);
  return details;
}

我们发现,错误是通过FlutterError.reportError方法上报的,继续跟踪:


static void reportError(FlutterErrorDetails details) {
  ...
  if (onError != null)
    onError(details); //调用了onError回调
}

我们发现onErrorFlutterError的一个静态属性,它有一个默认的处理方法 dumpErrorToConsole,到这里就清晰了,如果我们想自己上报异常,只需要提供一个自定义的错误处理回调即可,如:

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    reportError(details);
  };
 ...
}

这样我们就可以处理那些Flutter为我们捕获的异常了,接下来我们看看如何捕获其它异常。

其它异常捕获与日志收集

在Flutter中,还有一些Flutter没有为我们捕获的异常,如调用空对象方法异常、Future中的异常。在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch捕获,而异步异常则比较麻烦,如下面的代码是捕获不了Future的异常的:

try{
    Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
    print(e)
}

Dart中有一个runZoned(...) 方法,可以给执行对象指定一个Zone。Zone表示一个代码执行的环境范围,为了方便理解,读者可以将Zone类比为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常。下面我们看看runZoned(...)方法定义:

R runZoned<R>(R body(), {
    Map zoneValues, 
    ZoneSpecification zoneSpecification,
    Function onError,
}) 
  • zoneValues: Zone 的私有数据,可以通过实例zone[key]获取,可以理解为每个“沙箱”的私有数据。

  • zoneSpecification:Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出行为等,举个例子:

    下面是拦截应用中所有调用print输出日志的行为。

    main() {
      runZoned(() => runApp(MyApp()), zoneSpecification: new ZoneSpecification(
          print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
            parent.print(zone, "Intercepted: $line");
          }),
      );
    }

    这样一来,我们APP中所有调用print方法输出日志的行为都会被拦截,通过这种方式,我们也可以在应用中记录日志,等到应用触发未捕获的异常时,将异常信息和日志统一上报。ZoneSpecification还可以自定义一些其他行为,读者可以查看API文档。

  • onError:Zone中未捕获异常处理回调,如果开发者提供了onError回调或者通过ZoneSpecification.handleUncaughtError指定了错误处理回调,那么这个zone将会变成一个error-zone,该error-zone中发生未捕获异常(无论同步还是异步)时都会调用开发者提供的回调,如:

    runZoned(() {
        runApp(MyApp());
    }, onError: (Object obj, StackTrace stack) {
        var details=makeDetails(obj,stack);
        reportError(details);
    });

    这样一来,结合上面的FlutterError.onError我们就可以捕获我们Flutter应用中全部错误了!需要注意的是,error-zone内部发生的错误是不会跨越当前error-zone的边界的,如果想跨越error-zone边界去捕获异常,可以通过共同的“源”zone来捕获,如:

    var future = new Future.value(499);
    runZoned(() {
        var future2 = future.then((_) { throw "error in first error-zone"; });
        runZoned(() {
            var future3 = future2.catchError((e) { print("Never reached!"); });
        }, onError: (e) { print("unused error handler"); });
    }, onError: (e) { print("catches error of first error-zone."); });
    

总结

我们最终的异常捕获和上报代码大致如下:

void collectLog(String line){
    ... //收集日志
}
void reportErrorAndLog(FlutterErrorDetails details){
    ... //上报错误和日志逻辑
}

FlutterErrorDetails makeDetails(Object obj, StackTrace stack){
    ...// 构建错误信息
}

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    reportErrorAndLog(details);
  };
  runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
        collectLog(line); // 收集日志
      },
    ),
    onError: (Object obj, StackTrace stack) {
      var details = makeDetails(obj, stack);
      reportErrorAndLog(details);
    },
  );
}
收藏
评论区

相关推荐

Flutter开发必备Dart基础:Dart快速入门
<h1概述</h1 <pDart从2.0开始变为强类型语言,静态类型。这点和Java、C等比较相似。也就是说在编译时就已经知道变量的类型那么就是静态类型语言。开发人员在开发的时候需要指定变量的类型。这有什么优点呢? 就是所有类型检查都可以通过编译器来完成。可以提前预报一些琐碎的错误。<br 同时Dart还是面向对象的编程语言。像python、Java、Kol
Flutter开发必备Dart基础:Dart快速入门
本文首发于微信公众号「Android开发之旅」,欢迎关注 ,获取更多技术干货 Jetpack版WanAndroid项目地址:Android Jetpack架构开发组件化应用实战(https://links.jianshu.com/go?tohttps%3A%2F%2Fgithub.com%2Fwinlee28%2FJetpackWanAndroi
flutter -- dart基础之dart简介和安装
Dart介绍: Dart是由谷歌开发的计算机编程语言,它可以被用于web、服务器、移动应用 和物联网等领域的开发。 Dart诞生于2011年,号称要取代JavaScript。但是过去的几年中一直不温不火。直到Flutter的出现现在被人们重新重视。 要学Flutter的话我们必须首先得会Dart。 da
Dart | 浅析dart中库的导入与拆分
前言 最近十分热门的跨平台框架使用了一门比较生僻的编程语言dart。dart语言本身深受早期一些编程语言的影响。特别是Smalltalk,Java和JavaScript。我是从Java语言向dart过度的,一开始感觉很不错,快速就对这门语言有了一个初步的认识,并能够写小段代码了。 但在flutter的不断学习过程中,我遇到了不少因为dart的一些语
flutter 第一步 安装环境
前言 如果想开发flutter 先购买好梯子,配置好代理还是会经常出各种问题! 安装环境 java 环境安装 下载地址(https://links.jianshu.com/go?tohttps%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fjava%2Fjavase%2Fdownloads%2Fjdk8down
Flutter如何引用第三方库并使用
Flutter官网点击访问(https://link.jianshu.com/?thttps%3A%2F%2Fflutter.io) 如何引用并安装第三方库 pubspec.yaml管理第三方库 在pubspec.yaml中添加第三方库名称及版本号。 例如添加第三方库english_words dependencies: flut
Flutter/Dart - Dart中一个类实现多个接口 以及Dart中的Mixins
Dart中implements实现多个接口 abstract class A { String name; printA(); } abstract class B { printB(); } class C implements A,B{ @override String name;
Flutter 2.0 下混合开发浅析
多余的前言 Flutter 2.0 发布时,其中最受大家关注之一的内容就是 AddtoApp 相关的更新,因为除了热更新之外,Flutter 最受大家诟病的就是混合开发体验不好。 为什么不好呢?因为 Flutter 的控件渲染直接脱离了原生平台,也就是无论页面堆栈和渲染树都独立于平台运行,这固然给 Flutter 带来了较好的跨平台体验
【Flutter实战】初识Flutter
1.2 初识Flutter 1.2.1 Flutter简介Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 native扩展。同时 Flu
【Flutter 实战】Dart语言简介
1.4 Dart语言简介在之前我们已经介绍过Dart语言的相关特性,读者可以翻看一下,如果读者已经熟悉Dart语法,可以跳过本节,如果你还不了解Dart,也不用担心,按照笔者经验,如果你有过其他编程语言经验(尤其是Java和JavaScript)的话会非常容易上手Dart。当然,如果你是iOS开发者,也不用担心,Dart中也有一些与Swift比较相似的特
【Flutter实战】调试Flutter应用
2.5 调试Flutter应用有各种各样的工具和功能来帮助调试Flutter应用程序。 Dart 分析器在运行应用程序前,请运行flutter analyze测试你的代码。这个工具是一个静态代码检查工具,它是dartanalyzer工具的一个包装,主要用于分析代码并帮助开发者发现可能的错误,比如,Dart分析器大量使用了代码中的类型注释来帮
【Flutter实战】调试Flutter应用
2.5 调试Flutter应用有各种各样的工具和功能来帮助调试Flutter应用程序。 Dart 分析器在运行应用程序前,请运行flutter analyze测试你的代码。这个工具是一个静态代码检查工具,它是dartanalyzer工具的一个包装,主要用于分析代码并帮助开发者发现可能的错误,比如,Dart分析器大量使用了代码中的类型注释来帮
【Flutter实战】Dart线程模型及异常捕获
2.6 Flutter异常捕获在介绍Flutter异常捕获之前必须先了解一下Dart单线程模型,只有了解了Dart的代码执行流程,我们才能知道该在什么地方去捕获异常。 2.6.1 Dart单线程模型在Java和ObjectiveC(以下简称“OC”)中,如果程序发生异常且没有被捕获,那么程序将会终止,但是这在Dart或JavaScript中则不会!
Flutter - 深入理解Flutter应用启动
基于Flutter 1.5,从源码视角来深入剖析flutter应用的启动流程,相关源码目录见文末附录一、概述上一篇文章 已经介绍了FlutterApplication和FlutterActivity的onCreate()方法执行过程, 并触发Flutter引擎的启动,并最终执行到runApp(Widget app)方法,这才刚刚开始执行dart的
Flutter - 深入理解Dart虚拟机启动
基于Flutter 1.5,从源码视角来深入剖析引擎启动中的Dart虚拟机启动流程,相关源码目录见文末附录一、概述 1.1 Dart虚拟机概述Dart虚拟机拥有自己的Isolate,完全由虚拟机自己管理的,Flutter引擎也无法直接访问。Dart的UI相关操作,是由Root Isolate通过Dart的C调用,或者是发送消息通知的方式