c语言捕获崩溃位置,Flutter异常捕获和Crash崩溃日志收集

前言

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

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,发现如下代码:

/// 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或者State的build方法,并更新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方法,追踪源码:

static FlutterExceptionHandler onError = dumpErrorToConsole;

复制代码

onError是FlutterError的一个静态属性,它默认的处理方法是dumpErrorToConsole。如果我们更改异常的处理方式,提供一个自定的错误回调就可以了。

//在Flutter app入口配置自定义的异常上报回调(customerReport)

void main(){

FlutterError.onError = (FlutterErrorDetails details) {

customerReport(details);

};

runApp(MyApp());

}

复制代码

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

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

虽然Flutter为我们在很多关键的方法进行了异常捕获,但遗憾的是,它并不能为我们捕获并发异常。同步异常可以通过try/catch捕获,而异步异常则不会被捕获:

try{

Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));

}catch (e){

print(e)

}

复制代码

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

R runZoned(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)

},

),

);

复制代码

这样使用onError和zoneSpecification搭配可以实现记录日志和手机崩溃信息:

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符号表

作者:白瑞德

链接:https://juejin.cn/post/6844903940497244167

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值