捕获Flutter错误
重写FlutterError的onError即可,如下:
import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() { FlutterError.onError = (FlutterErrorDetails details) { FlutterError.dumpErrorToConsole(details); if (kReleaseMode) ... //处理线上错误,如统计上传 }; runApp(MyApp()); } 复制代码
上面我们重写了FlutterError.onError,这样就可以捕获到错误,第一行代码就是将error展示到控制台,这样我开发时就会在控制台很方便的看到错误。下面代码就是在线上环境下,对错误进一步处理,比如统计上传。
自定义ErrorWidget
上面我们知道,构建时发生错误会默认展示一个错误页面,但是这个页面很不友好,我们可以自定义一个错误页面。定义一个自定义的 error widget,以当 builder 构建 widget 失败时显示,请使用 MaterialApp.builder。
class MyApp extends StatelessWidget { ... @override Widget build(BuildContext context) { return MaterialApp( ... builder: (BuildContext context, Widget widget) { Widget error = Text('...rendering error...'); if (widget is Scaffold || widget is Navigator) error = Scaffold(body: Center(child: error)); ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error; return widget; }, ); } } 复制代码
在App下的builder中,自定义一个error页面,然后赋值给ErrorWidget.builder即可。这样再出现错误的时候就可以展示一个友好的页面。
无法捕获的错误
假设一个 onPressed 回调调用了异步方法,例如 MethodChannel.invokeMethod (或者其他 plugin 的方法):
OutlinedButton( child: Text('Click me!'), onPressed: () async { final channel = const MethodChannel('crashy-custom-channel'); await channel.invokeMethod('blah'); }, ), 复制代码
如果 invokeMethod 抛出了错误,它不会传递至 FlutterError.onError,而是直接进入 runApp 的 Zone。
如果你想捕获这样的错误,请使用 runZonedGuarded。代码如下:
import 'dart:async'; void main() { runZonedGuarded(() { runApp(MyApp()); }, (Object error, StackTrace stack) { ... //处理错误 }); } 复制代码
请注意,如果你的应用在 runApp 中调用了 WidgetsFlutterBinding.ensureInitialized()
方法来进行一些初始化操作(例如 Firebase.initializeApp()
),则必须在 runZonedGuarded 中调用 WidgetsFlutterBinding.ensureInitialized()
:
runZonedGuarded(() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(MyApp()); } 复制代码
如果 WidgetsFlutterBinding.ensureInitialized()
在外部调用,错误将不会被捕获到。
完整处理流程
如果要处理上面全部问题,则先通过runZonedGuarded处理异步错误,再通过FlutterError.onError处理,这些错误都通过一个我们自定义的MyErrorsHandler类来集中处理即可,比如统计上传等。然后在app中还需要定义一个友好的错误页面。