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反常
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()办法,将反常信息展现的更加友爱一点,咱们能够凭借它来做一些格式化的操作。
3.3版本API的改善
官方的API更新如下:
docs.flutter.dev/testing/err…
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');
},
);
}
}