Flutter 端异常捕获最佳实践 & Exception率计算

3,790 阅读1分钟

Flutter 端异常捕获方式

Flutter中的异常分为 同步异常 和 异步异常,异常类型又分为:

  1. RangeError ;dart异常

  2. FlutterErrorDetails;页面渲染异常

  3. MissingPluginException; 服务异常

  4. DioError ;Dio请求异常

可由error.runtimeType.toString()获取异常类型;

通常情况可以使用try-catch来捕获同步异常,异步异常需要使用flutter的其它api来捕获,下面汇总了常用的异常捕获方式:

使用 try-catchf 捕获异常

与Java、OC语言类似,Dart中也提供了try-catch代码块,使用方式也比较简单:

try {
     //抛出异常
    throw Exception("This is a test exception");
} 
catch (error,stack) {
//异常上报
}

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为我们捕获的异常了,接下来我们看看如何捕获其它异常。

另外:如果我们需要自定义错误页面,则需要重写 ErrorWidget.builder

///自定义红屏异常
ErrorWidget.builder = (FlutterErrorDetails details) {
  return MaterialApp(
    title: 'Error Widget',
    theme: ThemeData(
      primarySwatch: Colors.red,
    ),
    home: Scaffold(
      appBar: AppBar(
        title: Text('Widget渲染异常!!!'),
      ),
      body: getErrorWidget(details.toString()),
    ),
  );
};

使用 Future API 捕获异常

Future API 提供了 catchError 方法用于捕获使用 Future 时抛出的异步异常:

Future.delayed(Duration(seconds: 1))
.then((e)=>print(e.toString()))
.catchError((){
  //处理异常
}

使用 Zone 捕获未处理异常

Flutter 提供了 Zone.runZoned 方法(dart:async 包)用于捕获所有未处理的异常信息,也可以捕获、拦截或修改一些代码行为;

R runZoned<R>(R body(), {
    Map zoneValues, 
    ZoneSpecification zoneSpecification,
    Function onError,
})

其中,zoneValuesZone 的私有属性,zoneSpecificationZone 的一些配置信息,可以自定义一些代码行为,onErrorZone 中未捕获异常的回调方法。

如果开发者提供了 onError 回调方法或者通过 ZoneSpecification.handleUncaughtError 指定了错误处理的回调方法,那么这个 Zone 将会变为一个 error zone,无论是同步环境还是异步环境,该 error zone 都会在出现未捕获的异常信息时调用:

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

注意:

当你在debug模式运行带有异常代码块时,并不会走到onError代码块中,这是因为在debug模式下,flutter框架捕获了很多异常,并打印到控制台(有时在UI上以红色显示),没有重新抛出,所以你的代码捕获不到这些异常;解决办法是使用Release模式运行就可以了!

Flutter 异常收集最佳实践

目前异常信息收集分为:

自动收集

  1. 自动收集的含义是捕获全局代码未捕获的异常;
  2. flutter中使用runZoned的方式进行全局捕获;

手动收集

  1. 使用try-catch收集指定代码块的异常;

Flutter 异常捕获关键代码

红屏异常捕获

以下代码放在main.dart中,main()函数外

///创建错误widget body
Widget getErrorWidget(dynamic details) {
  return Container(
    color: Colors.white,
    child: Center(
      child: Text("出错啦!!!",style: TextStyle(color: Colors.black,fontSize: 26)),
    )
  );
}

以下代码放在main()函数中

///自定义红屏异常
ErrorWidget.builder = (FlutterErrorDetails details) {
  ExceptionReporter.reportError(details, null);
  return MaterialApp(
    title: 'Error Widget',
    theme: ThemeData(
      primarySwatch: Colors.red,
    ),
    home: Scaffold(
      appBar: AppBar(
        title: Text('Widget渲染异常!!!'),
      ),
      body: getErrorWidget(details.toString()),
    ),
  );
};

其它异常捕获

以下代码放在main()函数中

bool isProduction = const bool.fromEnvironment("dart.vm.product");
FlutterError.onError = (FlutterErrorDetails details) async {
    if (isProduction) {
      Zone.current.handleUncaughtError(details.exception, details.stack);
    }
    else {
      FlutterError.dumpErrorToConsole(details);
    }
};

///初始化Sentry
ExceptionReporter.initSentryWithUid('1001');

runZoned(() async {
    runApp(MyApp());
  }, onError: (dynamic error,StackTrace stackTrace) async {
    showToast(">>>"+error.toString());
    //Sentry上报
    ExceptionReporter.reportError(error, stackTrace);
  });

exception_reporter.dart 代码文件:

import 'package:flutter_study_app/utils/platform_utils.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry/sentry.dart' as Sentry;

var _sentryClient = Sentry.SentryClient(
    dsn: 'http://d5c4bd2894c54412b7139e15cd356d0f@192.168.0.33:9000/5'
);

class ExceptionReporter {
  /// 初始化Sentry
  /// 填写 uid
  static void initSentryWithUid(String uid) {
    _sentryClient.userContext = Sentry.User(id: uid);
  }

  static Future<Null> reportError(dynamic error, dynamic stackTrace) async {
    Map<String, dynamic> deviceInfo = await PlatformUtils
        .getMapFromDeviceInfo();
    String appVersion = await PlatformUtils.getAppVersion();
    /// 自定义需要上报的信息
    Map<String, dynamic> _errMap = {
      '\n 错误类型 \n': error.runtimeType.toString(),
      '\n 应用版本 \n': appVersion,
      '\n 设备信息 \n': deviceInfo,
      '\n 错误信息 \n': error.toString()
    };
    // Sentry上报
    //_sentryClient.captureException(exception: '$_errMap',stackTrace: stackTrace);
    final SentryResponse response = await _sentryClient.capture(
      event: Event(
        exception: '$_errMap',
        stackTrace: stackTrace,
      ),
    );
    // 上报结果处理
    if (response.isSuccessful) {
      print('Success! Event ID: ${response.eventId}');
    } else {
      print('Failed to report to Sentry.io: ${response.error}');
    }
  }
}

FlutterException率的计算

FlutterException率 = exception发生次数 / flutter页面PV

分子:exception发生次数(已过滤掉白名单)

Flutter内部assert、try-catch和一些异常逻辑的地方会统一调用FlutterError.onError

通过重定向FlutterError.onError到自己的方法中监测exception发生次数,并上报exception信息

分母:flutter页面PV

参考文献:

www.it1352.com/2028487.htm…

Flutter中文网

www.jianshu.com/p/d51395d57…

blog.csdn.net/zhaowei121/…