Flutter异常捕获和Crash崩溃日志收集

前言

和Android中的Java语言相似,Dart中也能够经过try/catch/finally来捕获代码块异常。不一样的是在Dart中发生异常的时候flutter APP并不会崩溃。在个人实践中,debug版中的Dart异常会表现为红屏加异常信息,而release版则是空白的白屏。下面咱们就从源码追溯Flutter的异常和捕获android

Flutter捕获的异常

Flutter为咱们提供了部分异常捕获。在flutter开发中你们确定遇到过屏幕编程红色并带有错误信息的状况,甚至在Widget宽度越界时也会出现这样的错误提示界面。虽然代码出现了错误,可是并不会致使APP崩溃,Flutter会帮咱们捕获异常。至于其中的原理,就须要咱们去看一下源码了。 以StatelessWidget为例,Widget会建立对应的Element,(至于为何要进入Element里面,请你们自行了解Widget的绘制原理)其代码以下:编程

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree. /// /// It is uncommon for subclasses to override this method. @override StatelessElement createElement() => StatelessElement(this); .... } 复制代码

追根溯源StatelessElement父类到ComponentElement,发现以下代码:bash

/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
@override
void performRebuild() {
 if (!kReleaseMode && debugProfileBuildsEnabled)
    ...
    try {
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack));
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack));
      _child = updateChild(null, built, slot);
    }
}
复制代码

根据注释不难发现,这个方法用来调用StatelessWidget或者Statebuild方法,并更新Widget树。不难发现,代码使用了try/catch包裹了built = build();也就是包裹了build执行的方法。当捕获到异常时使用ErrorWidget弹出错误提示。不难发现ErrorWidget.builder接受了一个_debugReportException方法返回的FlutterErrorDetails,并展现异常——这就是咱们上文提到的红屏吧。进入_debugReportException方法内继续追踪咱们须要的异常信息:网络

FlutterErrorDetails _debugReportException(
  DiagnosticsNode 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上报。进入该方法:并发

/// Calls [onError] with the given details, unless it is null.
static void reportError(FlutterErrorDetails details) {
    assert(details != null);
    assert(details.exception != null);
    if (onError != null)
      onError(details);
  }
复制代码

最后调用了onError方法,追踪源码:app

static FlutterExceptionHandler onError = dumpErrorToConsole;
复制代码

onErrorFlutterError的一个静态属性,它默认的处理方法是dumpErrorToConsole。若是咱们更改异常的处理方式,提供一个自定的错误回调就能够了。less

//在Flutter app入口配置自定义的异常上报回调(customerReport)
void main(){
  FlutterError.onError = (FlutterErrorDetails details) {
    customerReport(details);
  };
  runApp(MyApp());
}
复制代码

在Flutter app入口配置自定义的异常上报回调customerReport,至于异常的上报,是在Flutter中直接请求网络仍是与原生交互,咱们暂不进行讨论。至此,咱们就能够收集和处理那些Flutter为咱们捕获的异常了。异步

Flutter没有捕获的异常(并发异常)

虽然Flutter为咱们在不少关键的方法进行了异常捕获,但遗憾的是,它并不能为咱们捕获并发异常。同步异常能够经过try/catch捕获,而异步异常则不会被捕获:ide

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

上面的代码是捕获不了Future异常的。怎么办? 幸运的是,Flutter中与一个Zone的概念,Dart中可经过Zone表示指定代码执行的环境,不一样的Zone代码上下文是不一样的互不影响。相似一个沙盒概念,不一样沙箱的之间是隔离的,沙箱能够捕获、拦截或修改一些代码行为,咱们能够再这里面捕获全部没有被处理过的异常。 看一下runZoned的源码:post

R runZoned<R>(R body(),{
    Map zoneValues,
    ZoneSpecification zoneSpecification, 
    Function onError
    }
)
复制代码
  • zoneValues: Zone 的私有数据,能够经过实例zone[key]获取,能够理解为每一个“沙箱”的私有数据。
  • zoneSpecification:Zone的一些配置,能够自定义一些代码行为,好比拦截日志输出行为等
  • onError:Zone中未捕获异常处理回调,若是开发者提供了onError回调或者通

咱们能够经过onError捕获异常就像下面这样:

runZoned(
      () {
        Future.error("error");
      },
      onError: (dynamic e, StackTrace stack) {
        reportError(e, stack);
      },
    );
复制代码

咱们可让runApp运行在Zone中,这样就能够捕获咱们Flutter应用中所有错误了!

runZoned(() {
    runApp(MyApp());
}, onError: (Object obj, StackTrace stack) {
    reportError(e, stack);
});
复制代码

zoneSpecification则给了咱们带来了更大的可能性,它提供了forkedZone的规范,使用在此类中给出的实力做为回调覆盖Zone中的默认行为。处理程序上具备相同命名方法的方法。例如:拦截应用中全部调用print输出日志的行为:

runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
            report(line)
      },
    ),
  );
复制代码

这样使用onErrorzoneSpecification搭配能够实现记录日志和手机崩溃信息:

void main() {
  runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
            report(line)
      },
    ),
    onError: (Object obj, StackTrace stack) {
      customerReport(e, stack);
    }
  );
}
复制代码

Flutter engine 异常捕获

flutter engine部分的异常,以Android为例,主要为libfutter.so发生的错误。这部分发生的异常的捕获方式和原生发生的异常的捕获方式同样,能够借用Bugly等实现。

总结

经过上述介绍,Flutter的异常捕获整理以下:

异常收集

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    customerReport(details);
  };
  runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
            report(line)
      },
    ),
    onError: (Object obj, StackTrace stack) {
      customerReport(e, stack);
    }
  );
}
复制代码

异常上报

其中异常的上报可使用MethodChannel传递给Native,实现方式略,有兴趣的课参考:Flutter与android之间的通信 而在我负责的项目中,我使用Bugly来收集异常信息,咱们能够将native接收到的Flutter异常交给Bugly上报。 而收集到的崩溃信息则须要配置符号表(symbols)还原堆栈以肯定崩溃信息。详情请参考:构建系统加入Flutter符号表

相关文章
相关标签/搜索