flutter: 捕捉异常:处理Widget构建时的错误

一,代码:

1,main

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'services/AuthService.dart';
import 'routes/routes.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

void main() {

  runZonedGuarded(() {
    // 第二层:Flutter框架自身的错误回调
    FlutterError.onError = (FlutterErrorDetails details) {
      print("捕捉到了异常");
      FlutterError.presentError(details); // 开发时显示红屏
      print('详情打印开始:');
      String detail = details.toString();
      print(detail);
      print('详情打印结束:');
    };

    // 第三层:Widget构建时的错误边界
    ErrorWidget.builder = (FlutterErrorDetails details) {
      // 当Widget构建失败时,展示我们自定义的错误界面,而不是崩溃
      print('Widget错误时详情打印开始:');
      String detail = details.toString();
      print(detail);
      print('Widget错误时详情打印结束:');
      return ErrorRecoveryWidget(details);
    };

    runApp(MyApp());
  }, (error, stackTrace) {
    // 处理所有未被前面捕获的异常
    //_handleZoneError(error, stackTrace);
    //print("处理所有未被前面捕获的异常");
    //print(error);
    print('未被前面捕获的异常: $error');
    print('未被前面捕获的异常的堆栈: $stackTrace');
  });
}

class ErrorRecoveryWidget extends StatelessWidget {
  final FlutterErrorDetails errorDetails;
  final VoidCallback? onRetry;

  const ErrorRecoveryWidget(
      this.errorDetails, {
        this.onRetry,
        Key? key,
      }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Container(
        color: Colors.grey[100],
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.error_outline,
              size: 64,
              color: Colors.red[400],
            ),
            const SizedBox(height: 20),
            Text(
              '页面加载失败',
              style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                color: Colors.red[700],
              ),
            ),
            const SizedBox(height: 12),
            Text(
              errorDetails.exceptionAsString(),
              textAlign: TextAlign.center,
              style: const TextStyle(color: Colors.grey),
              maxLines: 3,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 24),
            if (onRetry != null)
              ElevatedButton.icon(
                onPressed: onRetry,
                icon: const Icon(Icons.refresh),
                label: const Text('重试'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                ),
              ),
            const SizedBox(height: 16),
            TextButton(
              onPressed: () {
                showErrorDetailsDialog(context, errorDetails);
              },
              child: const Text('查看错误详情'),
            ),
          ],
        ),
      ),
    );
  }

  // 提供一个对话框展示详细的错误信息,方便调试
  void showErrorDetailsDialog(BuildContext context, FlutterErrorDetails details) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('错误详情'),
        content: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text('异常类型:', style: TextStyle(fontWeight: FontWeight.bold)),
              SelectableText(details.exception.runtimeType.toString()),
              const SizedBox(height: 12),
              const Text('异常信息:', style: TextStyle(fontWeight: FontWeight.bold)),
              SelectableText(details.exception.toString()),
              const SizedBox(height: 12),
              const Text('堆栈跟踪:', style: TextStyle(fontWeight: FontWeight.bold)),
              SizedBox(
                height: 200,
                child: SingleChildScrollView(
                  child: SelectableText(details.stack.toString()),
                ),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('关闭'),
          ),
          TextButton(
            onPressed: () {
              Clipboard.setData(ClipboardData(
                  text: '${details.exception}\n\n${details.stack}'
              ));
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已复制到剪贴板')),
              );
            },
            child: const Text('复制'),
          ),
        ],
      ),
    );
  }
}



class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(750, 1334),
      minTextAdapt: true,
      splitScreenMode: true,
      // Use builder only if you need to use library outside ScreenUtilInit context
      builder: (_ , child) {
        return GetMaterialApp(
          debugShowCheckedModeBanner: false, //去除debug图标
          defaultTransition: Transition.rightToLeft,   //指定动画
          theme: ThemeData(primarySwatch: Colors.red),
          initialRoute: "/",    //初始化页面
          getPages: APPage.routes,   //路由
        );
      },
    );
  }

}

2,homepage

用来触发异常:

在这里,我们犯了一个明显的错误,在我们知道的 null 小部件上使用了 bang 运算符(!),
这导致在非发布模式下出现红屏,在发布模式下出现灰屏。

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

class HomePage extends StatefulWidget {
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>  {

  @override
  Widget build(BuildContext context) {
    const widget = null;
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("home页面"),
      ),
      body:widget!
    );
  }

}

 

二,测试效果:

imageimage

 

posted @ 2026-03-07 20:55  刘宏缔的架构森林  阅读(0)  评论(0)    收藏  举报