> 文章列表 > Flutter开发:Error的捕获及处理

Flutter开发:Error的捕获及处理

Flutter开发:Error的捕获及处理

前言

在上一篇文章《Flutter混合开发:Android中如何启动Flutter》中我们学习了如果在Android项目中使用Flutter,在使用过程中经常会遇到各种错误,那么如何处理这些错误?

Flutter 框架可以捕获运行期间的错误,包括构建期间、布局期间和绘制期间。

关于Flutter错误的处理包含三个部分:

  • onError:处理主线程发生的错误
  • ErrorWidget:错误发生时的展示页面
  • Zone:处理其他异步线程中(onError无法处理)的错误

下面来看看如何处理。

onError

所有 Flutter 的错误均会被回调方法 FlutterError.onError 捕获。默认情况下,会调用 FlutterError.dumpErrorToConsole 方法,正如方法名表示的那样,将错误转储到当前的设备日志中。当从 IDE 运行应用时,检查器重写了该方法,错误也被发送到 IDE 的控制台,可以在控制台中检查出错的对象。

所以捕获这部分错误只需要重写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

当构建期间发生错误时,回调函数 ErrorWidget.builder 会被调用,来生成一个新的 widget,用来代替构建失败的 widget。默认情况,debug 模式下会显示一个红色背景的错误页面, release 模式下会展示一个灰色背景的空白页面。如下:
Flutter开发:Error的捕获及处理

上面我们知道,构建时发生错误会默认展示一个错误页面,但是这个页面很不友好,我们可以自定义一个错误页面。定义一个自定义的 error widget,以当 builder 构建 widget 失败时显示,请使用 MaterialApp.builder。

class MyApp extends StatelessWidget {
...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即可。这样再出现错误的时候就可以展示一个友好的页面,提示用户重启或者升级应用等。

Zone

如果在调用堆栈上没有 Flutter 回调的情况下发生错误(这里可以理解为FlutterError.onError仅仅可以捕获主线程的错误,而其他异步线程的错误则需要Zone来捕获),它们由发生区域的 Zone 处理。 Zone 在默认情况下仅会打印错误,而不会执行其他任何操作。

假设一个 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() 在外部调用,错误将不会被捕获到。

完整流程

所以flutter中完整的处理错误流程实际上是分三步的

  1. 先通过runZonedGuarded处理异步错误
  2. 再通过FlutterError.onError处理
    (runZonedGuarded和onError捕获的这些错误可以通过一个我们自定义的MyErrorsHandler类来集中处理即可,比如统计上传等。)
  3. 最后还需要自定义一个友好的错误页面来取代默认错误页面。

完整代码如下:

import 'dart:io';import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';void main() {runZonedGuarded(() async {WidgetsFlutterBinding.ensureInitialized();//todo 这里是其他初始化代码FlutterError.onError = (FlutterErrorDetails details) {FlutterError.dumpErrorToConsole(details);//处理错误,比如统计上传。MyErrorsHandler};runApp(MyApp());}, (Object error, StackTrace stack) {//处理错误,比如统计上传。MyErrorsHandler});
}class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(//todo 这里是其他设置,如样式等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;},);}
}