Flutter异常捕获runZoned

今天开始看gsy_github_app_flutter
入口文件就来了个runZoned,代码如下

void main() {
  runZoned(() {
    ErrorWidget.builder = (FlutterErrorDetails details) {
      Zone.current.handleUncaughtError(details.exception, details.stack);
      return ErrorPage(
          details.exception.toString() + "\n " + details.stack.toString(), details);
    };
    runApp(ConfigWrapper(
      child: FlutterReduxApp(),
      config: EnvConfig.fromJson(config),
    ));
  }, onError: (Object obj, StackTrace stack) {
    print(obj);
    print(stack);
  });
}

ErrorWidget.builder

Flutter就会自动弹出一个错误界面,这是因为Flutter已经在执行build方法时添加了异常捕获,最终的源码如下:

@override
void performRebuild() {
...
try {
//执行build方法
built = build();
} catch (e, stack) {
// 有异常时则弹出错误提示
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
}
...
}
可以看到,在发生异常时,Flutter默认的处理方式时弹一个ErrorWidget
Flutter就会自动弹出一个错误界面,这是因为Flutter已经在执行build方法时添加了异常捕获,最终的源码如下:

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

可以看到,在发生异常时,Flutter默认的处理方式时弹一个ErrorWidget,所以ErrorWidget.builder就是用于生成widget时错误时替换默认错误显示的,就是替换那个全红的报错界面。

image.png

runZoned

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

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

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

R runZoned(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."); }
);

本文资料来源

你可能感兴趣的:(Flutter异常捕获runZoned)