赞
踩
点击上方蓝字关注我,知识会给你力量
Flutter App层和Framework层的异常,通常是不会引起Crash的,但是Engine层的异常会造成Crash。而Flutter Engine部分的异常,主要是libfutter.so发生的异常,这部分的异常,在Dart层无法捕获,一般会交给类似Bugly这样的平台来收集。
我们能主动监控的,主要是Dart层的异常,这些异常虽然不会让App crash,但是统计这些异常对于提高我们的用户体验,是非常有必要的。
对于同步异常来说,直接使用try-catch就可以捕获异常,如果要指定捕获的异常类型,可以使用on关键字。但是,try-catch不能捕获异步异常,就像下面的代码,是无法捕获的。
- try {
- Future.error("error");
- } catch (e){
- print(e)
- }
这和在Java中,try-catch捕获Thread中的异常类似,对于异步异常来说,只能使用Future的catchError或者是onError来捕获异常,代码如下所示。
Future.delayed(Duration(seconds: 1)).then((value) => print(value), onError: (e) {});
Dart的执行队列是一个单线程模型,所以在事件循环队列中,当某个Task发生异常并没有被捕获时,程序并不会退出,只是当前的Task异常中止,也就是说一个Task发生的异常是不会影响其它Task执行的。
Widget在Build过程中如果发生异常,例如在build函数中出错(throw exception),我们会看见一个深红色的异常界面,这个就是Flutter自带的异常处理界面,我们来看下源代码中,Flutter对这类异常的处理方式。在ComponentElement的实现中,我们找到performRebuild函数,这个是函数是build时所调用的,我们在这里,可以找到相关的实现。
如下所示,在执行到build()函数如果出错时,就会被catch,从而创建一个ErrorWidget。
再进入_debugReportException中一探究竟,你会发现,应用层的异常被catch之后,都是通过FlutterError.reportError来处理的。
在reportError中,会调用onError来处理,默认的处理方式是dumpErrorToConsole,它就是onError的默认实现。
❝在这里我们还能发现如何判断debug模式,看源码是不是很有意思。
❞
通过上面的源码,我们就可以了解到,当Flutter应用层崩溃后,SDK的处理,简而言之,就是会构建一个错误界面,同时回调onError函数。在这里,我们可以通过修改这个静态的回调函数,来创建自己的处理方式。
所以,很简单,我们只需要在main()中,执行下面的代码即可。
- var defaultError = FlutterError.onError;
- FlutterError.onError = (FlutterErrorDetails details) {
- defaultError?.call(details);// 根据需要是否要保留default处理
- reportException(details);
- };
defaultError?.call(details)就是默认将异常日志打印到console的方法,如果不用,这里可以去掉。
前面我们看到了,在源代码中,Flutter自定义了一个ErrorWidget作为默认的异常界面,在平时的开发中,我们可以自定义ErrorWidget.builder,实现一个更友好的错误界面,例如封装一个统一的异常提示界面。
- ErrorWidget.builder = (FlutterErrorDetails details) {
- return MaterialApp(
- theme: ThemeData(primarySwatch: Colors.red),
- home: Scaffold(
- appBar: AppBar(
- title: const Text('出错了,请稍后再试'),
- ),
- body: SingleChildScrollView(
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(details.toString()), // 后续修改为统一的错误页
- )),
- ),
- );
- };
如上所示,通过修改ErrorWidget.builder,就可以将任意自定义的界面作为异常界面了。
前面讲到的,都是属于被捕获的异常,而有一些异常,在代码中是没有被捕获的,这就类似Android的UncaughtExceptionHandler,Flutter也提供了一个全局的异常处理钩子函数,所有的未捕获异常,无论是同步异常还是异步异常,都会在这里被监听。
在Dart中,SDK提供了一个Zone的概念,一个Zone就类似一个沙箱,在Zone里面,可以拥有独立的异常处理、print函数等等功能,多个Zone之间是彼此独立的,所以,我们只需要将App运行在一个Zone里面,就可以借助它的handleUncaughtError来处理所有的未捕获异常了。下面是使用Zone的一个简单示例。
- void main() {
- runZoned(
- () => runApp(const MyApp(color: Colors.blue)),
- zoneSpecification: ZoneSpecification(
- handleUncaughtError: (
- Zone self,
- ZoneDelegate parent,
- Zone zone,
- Object error,
- StackTrace stackTrace,
- ) {
- reportException(
- FlutterErrorDetails(
- exception: error,
- stack: stackTrace,
- ),
- );
- },
- ),
- );
- }
根据文档中的提升,可以使用runZonedGuarded来进行简化,代码如下所示。
- void main() {
- runZonedGuarded(
- () => runApp(const MyApp(color: Colors.blue)),
- (Object error, StackTrace stack) {
- reportException(
- FlutterErrorDetails(
- exception: error,
- stack: stack,
- ),
- );
- },
- );
- }
下面我们将前面的异常处理方式都合并到一起,并针对EngineGroup的多入口处理,封装一个类,代码如下所示。
- class SafeApp {
- run(Widget app) {
- ErrorWidget.builder = (FlutterErrorDetails details) {
- return MaterialApp(
- theme: ThemeData(primarySwatch: Colors.red),
- home: Scaffold(
- appBar: AppBar(
- title: const Text('出错了,请稍后再试'),
- ),
- body: SingleChildScrollView(
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(details.toString()), // 后续修改为统一的错误页
- )),
- ),
- );
- };
- FlutterError.onError = (FlutterErrorDetails details) {
- Zone.current.handleUncaughtError(details.exception, details.stack!);
- };
- runZonedGuarded(
- () => runApp(const MyApp(color: Colors.blue)),
- (Object error, StackTrace stack) {
- reportException(
- FlutterErrorDetails(
- exception: error,
- stack: stack,
- ),
- );
- },
- );
- }
- }
在这里,我们构建了下面这些异常处理的方式:
统一的异常处理界面
将Build异常统一转发到Zone中的异常处理函数来进行处理
将所有的未捕获异常记录
这样的话,我们在使用时,只需要对原始的App进行下调用即可。
void main() => SafeApp().run(const MyApp(color: Colors.blue));
这样就完成了异常处理的封装。
在Flutter侧,我们只是获取了异常的相关信息,如果需要上报,那么我们需要借助Channel,桥接的Native,使用Bugly或其它平台进行上报,我们可以借助Pigeon来进行处理,还不熟悉的朋友可以参考我前面的文章。
Flutter混编工程之高速公路Pigeon
Flutter混编工程之通讯之路
通过Channel,我们可以把异常数据报给Native侧,再让Native侧走自己的上报通道,例如Bugly等。
NativeCommonApi().reportException('------Flutter_Exception------\n${details.exceptionAsString()}\n${details.stack.toString()}');
同时,Flutter提供了exceptionAsString()方法,将异常信息展示的更加友好一点,我们可以借助它来做一些格式化的操作。
官方的API更新如下:
https://docs.flutter.dev/testing/errors
PlatformDispatcher.onError在以前的版本中,开发者必须手动配置自定义Zone才能捕获应用程序的所有异常和错误,但是自定义Zone对Dart核心库中的一些优化是有害的,这会减慢应用程序的启动时间。「在此版本中,开发者可以通过设置回调来捕获所有错误和异常,而不是使用自定义。」
所以,3.3之后,我们不用再设置Zone来捕获全局异常了,只用设置PlatformDispatcher.instance.onError即可。
- import 'package:flutter/material.dart';
- import 'dart:ui';
-
- Future<void> main() async {
- await myErrorsHandler.initialize();
- FlutterError.onError = (details) {
- FlutterError.presentError(details);
- myErrorsHandler.onErrorDetails(details);
- };
- PlatformDispatcher.instance.onError = (error, stack) {
- myErrorsHandler.onError(error, stack);
- return true;
- };
- runApp(const MyApp());
- }
-
- class MyApp extends StatelessWidget {
- const MyApp({super.key});
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- builder: (context, widget) {
- Widget error = const Text('...rendering error...');
- if (widget is Scaffold || widget is Navigator) {
- error = Scaffold(body: Center(child: error));
- }
- ErrorWidget.builder = (errorDetails) => error;
- if (widget != null) return widget;
- throw ('widget is null');
- },
- );
- }
- }
向大家推荐下我的网站 https://www.yuque.com/xuyisheng 点击原文一键直达
专注 Android-Kotlin-Flutter 欢迎大家访问
往期推荐
本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。
< END >
作者:徐宜生
更文不易,点个“三连”支持一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。