【Flutter开发】如何捕获处理Error

前言

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

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

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

  • onError:处理主线程发生的错误
  • ErrorWidget:错误发生时的展示页面
  • Zone:处理其他异步线程中(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.builder 会被调用,来生成一个新的 widget,用来代替构建失败的 widget。默认情况,debug 模式下会显示一个红色背景的错误页面, release 模式下会展示一个灰色背景的空白页面。如下:
【Flutter开发】如何捕获处理Error_第1张图片

上面我们知道,构建时发生错误会默认展示一个错误页面,但是这个页面很不友好,我们可以自定义一个错误页面。定义一个自定义的 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即可。这样再出现错误的时候就可以展示一个友好的页面,提示用户重启或者升级应用等。

异步线程错误

如果在调用堆栈上没有 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. 最后还需要自定义一个友好的错误页面来取代默认错误页面。

完整代码如下:
【Flutter开发】如何捕获处理Error_第2张图片

总结

可以看到再处理Flutter中的Error的时候,我们要分情况进行处理,主线程和异步线程的错误通过不同的方式进行捕获,但是处理可以统一。另外为了更友好的展示,可以重新定义一个错误页面来替换默认的错误页面,这样当出错的时候会显示一个很友好的页面来提示用户。

你可能感兴趣的:(flutter入门+进阶,flutter)