在 Flutter 里处理错误

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

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

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

如果在调用堆栈上没有 Flutter 回调的情况下发生错误,它们由发生区域的 Zone 处理。 Zone 在默认情况下仅会打印错误,而不会执行其他任何操作。

这些回调方法都可以被重写,通常在 void main() 方法中重写。

下面解释了所有的错误捕获类型。在最后的代码段可以用于处理所有类型的错误。尽管你可以直接复制粘贴代码段,但我们建议你先了解每种错误类型。

Flutter 导致的错误

例如,如果你想在 release 模式下发生错误时立刻关闭应用,可以使用下面的回调方法:

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    if (kReleaseMode) exit(1);
  };
  runApp(const MyApp());
}

// rest of `flutter create` code...

这个回调方法也可以上报错误到日志服务平台。更多信息可以查看文档 报错信息通过服务上传

自定义一个 ErrorWidget 以展示 build 时的错误

定义一个自定义的 error widget,以当 builder 构建 widget 失败时显示,请使用 MaterialApp.builder

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 StateError('widget is null');
      },
    );
  }
}

未被 Flutter 捕获的错误

假设一个 onPressed 回调调用了异步方法,例如 MethodChannel.invokeMethod (或者其他 plugin 的方法):

OutlinedButton(
  child: const Text('Click me!'),
  onPressed: () async {
    const channel = MethodChannel('crashy-custom-channel');
    await channel.invokeMethod('blah');
  },
)

如果 invokeMethod 抛出了错误,它不会传递至 FlutterError.onError,而是直接进入 runAppZone

如果你想捕获这样的错误,请使用 PlatformDispatcher.instance.onError

import 'package:flutter/material.dart';
import 'dart:ui';

void main() {
  MyBackend myBackend = MyBackend();
  PlatformDispatcher.instance.onError = (error, stack) {
    myBackend.sendError(error, stack);
    return true;
  };
  runApp(const MyApp());
}

处理所有类型的错误

如果你想在异常抛出时退出应用,并在 build 错误时展示自定义的 ErrorWidget,你可以在下面的代码片段的基础上定制你的处理:

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 StateError('widget is null');
      },
    );
  }
}