我正在参与「启航方案」

Flutter怎么捕获反常

  • 程序反常会怎样
    • 在Java中,程序产生反常且没有被捕获,那么程序将会停止。
    • 但在Dart或JavaScript中则不会,究其原因,这和它们的运行机制有关系,Java是多线程模型的编程语言,恣意一个线程触发反常且没被捕获时,整个进程就退出。
    • 但Dart和JavaScript不会,它们都是单线程模型,运行机制很类似(但有差异)。
  • flutter怎么捕获反常
    • Dart中能够经过try/catch/finally来捕获代码块反常,这个和其它编程语言类似。

Flutter框架反常捕获

  • Flutter 框架在很多关键的办法进行了反常捕获。
    • 举一个比如,当布局产生越界或不合规范时,Flutter就会自动弹出一个过错界面,这是因为Flutter已经在履行build办法时添加了反常捕获。
    • 终究的源码如下,具体看 ComponentElement 类中的 performRebuild() 办法。
    @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);
        },
      );
    }