Flutter错误处理机制

Flutter 的异常处理机制包括 同步异常异步异常 ,涉及多个层面,如 Dart 语言级别的异常捕获Flutter 框架的错误处理机制自定义异常处理 以及 错误上报

Dart语言级别的异常

同步异常

php 复制代码
dart
复制编辑
void main() {
  try {
    int result = int.parse('abc'); // 除以 0,会触发异常
  } catch (e, stackTrace) {
    print("捕获异常: $e");
    print("堆栈信息: $stackTrace");
  }
}

异步异常

异步 Future 任务的异常需要使用 .catchError()try-catch 捕获:

csharp 复制代码
dart
复制编辑
Future<void> asyncTask() async {
  try {
    await Future.delayed(Duration(seconds: 1));
    throw Exception("异步任务异常");
  } catch (e) {
    print("捕获 Future 异常: $e");
  }
}

void main() {
  asyncTask();
}

另一种方式是 .catchError()

javascript 复制代码
dart
复制编辑
Future<void> asyncTask() {
  return Future.delayed(Duration(seconds: 1))
      .then((_) => throw Exception("异步任务异常"))
      .catchError((e) {
        print("Future 捕获异常: $e");
      });
}

以上都是我们需要自己手动写try-catch的语句,但是有些时候,我们并不知道代码里面是否会抛异常,也就不会去写try-catch语句,flutter是否有一种兜底的机制,能catch住所有异常,然后我们再这里上报异常信息到服务器,答案是:有!

Flutter 框架级别的异常处理

Flutter 提供了一套全局异常处理机制,包括:

  • FlutterError.onError

  • runZonedGuarded

Flutter 框架错误 ( FlutterError.onError )

Flutter 框架内部的异常(例如 setState(),build()等框架 期间的异常)会由 FlutterError.onError 处理

ini 复制代码
dart
复制编辑
void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    app.error(
      'Flutter Error: $description',
      details.exception,
      details.stack,
    );
  };

  runApp(MyApp());
}

渲染异常

比如说布局溢出了,发生了异常,控制台会有类似输出

less 复制代码
I/flutter ( 12871 ): Flutter Error: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞══════════════════════ I/flutter ( 12871 ): The following assertion was thrown during layout: I/flutter ( 12871 ): A RenderFlex overflowed by 19280 pixels on the bottom. I/flutter ( 12871 ): I/flutter ( 12871 ): The relevant error-causing widget was: I/flutter ( 12871 ):   Column I/flutter ( 12871 ):   Column:file: /// Volumes/Lenovo/code/mproject/flutter/mflutter/lib/pages/ai _office_ page.dart:29:16  I/flutter ( 12871 ): I/flutter ( 12871 ): The overflowing RenderFlex has an orientation of Axis.vertical. I/flutter ( 12871 ): The edge of the RenderFlex that is overflowing has been marked in  I/flutter ( 12871 ): the rendering with a yellow and black striped pattern. This is  I/flutter ( 12871 ): usually caused by the contents being too big for the RenderFlex. I/flutter ( 12871 ): Consider applying a flex factor (e.g. using an Expanded widget) I/flutter ( 12871 ): to force the children of the RenderFlex to fit within the I/flutter ( 12871 ): available space instead of being sized to their natural size. I/flutter ( 12871 ): This is considered an error condition because it indicates that I/flutter ( 12871 ): there is content that cannot be seen. If the content is  I/flutter ( 12871 ): legitimately bigger than the available space, consider clipping I/flutter ( 12871 ): it with a ClipR

屏幕上会出现警示条纹的情况

渲染函数被try-catch包围

build等widget生命周期出错

包括在build中如果出现了业务错误也会由FlutterError.onError接管

scss 复制代码
Widget build(BuildContext context) {
  int.parse('abc');
  return Scaffold();
}
bash 复制代码
错误是 ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════
I/flutter (13634): The following FormatException was thrown building
I/flutter (13634): AIOfficePage(dirty, state: _AIOfficePageState#69df9):
I/flutter (13634): Invalid radix-10 number (at character 1)
I/flutter (13634): abc

屏幕显示

本质还是Flutter框架层try-catch住异常,然后抛给FlutterError.onError

ini 复制代码
void performRebuild() {
  Widget? built;
  try {
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    built = build();
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    debugWidgetBuilderValue(widget, built);
  } catch (e, stack) {
    _debugDoingBuild = false;
    built = ErrorWidget.builder(
    //在这里会调用FlutterError.onError
      _reportException(
        ErrorDescription('building $this'),
        e,
        stack,
        informationCollector: () => <DiagnosticsNode>[
          if (kDebugMode)
            DiagnosticsDebugCreator(DebugCreator(this)),
        ],
      ),
    );
  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    super.performRebuild(); // clears the "dirty" flag
  }

GestureDector.onTap

dart 复制代码
T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
  T? result;
  try {
    assert(() {
      if (debugPrintRecognizerCallbacksTrace) {
        final String? report = debugReport != null ? debugReport() : null;
        // The 19 in the line below is the width of the prefix used by
        // _debugLogDiagnostic in arena.dart.
        final String prefix = debugPrintGestureArenaDiagnostics ? '${' ' * 19}❙ ' : '';
        debugPrint('$prefix$this calling $name callback.${ (report?.isNotEmpty ?? false) ? " $report" : "" }');
      }
      return true;
    }());
    result = callback();
  } catch (exception, stack) {
    InformationCollector? collector;
    assert(() {
      collector = () => <DiagnosticsNode>[
        StringProperty('Handler', name),
        DiagnosticsProperty<GestureRecognizer>('Recognizer', this, style: DiagnosticsTreeStyle.errorProperty),
      ];
      return true;
    }());
    FlutterError.reportError(FlutterErrorDetails(
      exception: exception,
      stack: stack,
      library: 'gesture',
      context: ErrorDescription('while handling a gesture'),
      informationCollector: collector,
    ));
  }
  return result;
}

从上面的例子可以看出来FlutterError.onError主要是处理Flutter框架在处理布局、渲染、点击函数等核心流程外面包裹了一层try-catch,在异常发生的时候去调用FlutterError.onError方法的

runGuardedZone

Flutter 中普通的 try-catch 只能捕获「同步代码」和「当前调用栈内的异步代码」异常,但对 FutureTimerisolate 等脱离当前调用栈的异步操作异常,try-catch 无法捕获;而 runZonedGuarded 可以监控整个「代码区域」内的所有异常(同步 + 异步),是全局异常捕获的核心手段。

scss 复制代码
runZonedGuarded(() {
  runApp(const MyApp());
}, (err, stackTrace) {
  print('zone错误 $stackTrace');
});

同步异常捕获

vbnet 复制代码
R? runZonedGuarded<R>(R body(), void onError(Object error, StackTrace stack),
    {Map<Object?, Object?>? zoneValues, ZoneSpecification? zoneSpecification}) {
  checkNotNullable(body, "body");
  checkNotNullable(onError, "onError");
  _Zone parentZone = Zone. _current;
  HandleUncaughtErrorHandler errorHandler = (Zone self, ZoneDelegate parent,
      Zone zone, Object error, StackTrace stackTrace) {
    try {
      parentZone.runBinary(onError, error, stackTrace);
    } catch (e, s) {
      if (identical(e, error)) {
        parent.handleUncaughtError(zone, error, stackTrace);
      } else {
        parent.handleUncaughtError(zone, e, s);
      }
    }
  };
  if (zoneSpecification == null) {
    zoneSpecification = ZoneSpecification(handleUncaughtError: errorHandler);
  } else {
    zoneSpecification = ZoneSpecification.from(zoneSpecification,
        handleUncaughtError: errorHandler);
  }
  //这里加了try-catch
  try {
    return _runZoned<R>(body, zoneValues, zoneSpecification);
  } catch (error, stackTrace) {
    //这里去执行异常处理
    onError(error, stackTrace);
  }
  return null;
}

异步异常捕获

以Future.delay为例

ini 复制代码
///sky_engine/lib/async/future.dart
factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
  if (computation == null && !typeAcceptsNull<T>()) {
    throw ArgumentError.value(
        null, "computation", "The type parameter is not nullable");
  }
  _Future<T> result = new _Future<T>();
  new Timer(duration, () {
    if (computation == null) {
      result._complete(null as T);
    } else {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    }
  });
  return result;
}


void _completeWithErrorCallback(
    _Future result, Object error, StackTrace? stackTrace) {
  //拿到和当前Zone绑定的错误处理回调去执行
  AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
  if (replacement != null) {
    error = replacement.error;
    stackTrace = replacement.stackTrace;
  } else {
    stackTrace ??= AsyncError.defaultStackTrace(error);
  }
  result._completeError(error, stackTrace);
}

Timer创建的时候去绑定Zone和Future回调的关系

dart 复制代码
factory Timer(Duration duration, void Function() callback) {
  if (Zone.current == Zone.root) {
    // No need to bind the callback. We know that the root's timer will
    // be invoked in the root zone.
    return Zone.current.createTimer(duration, callback);
  }
  return Zone.current
.createTimer(duration, Zone.current.bindCallbackGuarded(callback));
}
scss 复制代码
///sky_engine/lib/async/zone.dart
R _rootRun<R>(Zone? self, ZoneDelegate? parent, Zone zone, R f()) {
  if (identical(Zone. _current, zone)) return f();

  if (zone is! _Zone) {
    throw ArgumentError.value(zone, "zone", "Can only run in platform zones");
  }

  _Zone old = Zone. _enter(zone);
  try {
    return f();
  } finally {
    Zone. _leave(old);
  }
}

static _Zone _enter(_Zone zone) {
  assert(!identical(zone, _current));
  _Zone previous = _current;
  _current = zone;
  return previous;
}

static _Zone _current = _rootZone;

至于使用静态变量存放 _current 会不会导致 多线程 问题,答案是:不会

scss 复制代码
传统多线程模型(Java/C++):
┌─────────────────────────────────────────────────────────┐
│                    共享内存空间                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │  线程 1     │  │  线程 2     │  │  线程 3     │    │
│  │  共享变量   │  │  共享变量   │  │  共享变量   │    │
│  └─────────────┘  └─────────────┘  └─────────────┘    │
│         ↓                ↓                ↓             │
│    可能发生竞争条件、数据竞争、死锁等问题                │
└─────────────────────────────────────────────────────────┘

Dart Isolate 模型:
┌─────────────────────────────────────────────────────────┐
│  ┌─────────────────┐  ┌─────────────────┐              │
│  │   Isolate 1     │  │   Isolate 2     │              │
│  │  独立内存空间    │  │  独立内存空间    │              │
│  │  ┌───────────┐  │  │  ┌───────────┐  │              │
│  │  │Zone._current│  │  │  │Zone._current│  │              │
│  │  │  (独立)    │  │  │  │  (独立)    │  │              │
│  │  └───────────┘  │  │  └───────────┘  │              │
│  └─────────────────┘  └─────────────────┘              │
│         ↓                    ↓                          │
│    通过消息通信          通过消息通信                    │
│    (不共享内存)          (不共享内存)                    │
└─────────────────────────────────────────────────────────┘

综上,其实异常的捕获根本还是try-catch,只是不同的错误在不同的方法里面加了对应的try-catch,然后回调出来

sentry里面的接入

ai办公举例

dart 复制代码
static R? sentryRunZonedGuarded<R>(
  Hub hub,
  R Function() body,
  void Function(Object error, StackTrace stack)? onError, {
  Map<Object?, Object?>? zoneValues,
  ZoneSpecification? zoneSpecification,
}) {
  final sentryOnError = (exception, stackTrace) async {
    final options = hub.options;
    await _captureError(hub, options, exception, stackTrace);

    if (onError != null) {
      onError(exception, stackTrace);
    }
  };

  final userPrint = zoneSpecification?.print;

  ......
  return runZonedGuarded(
    body,
    sentryOnError,
    zoneValues: zoneValues,
    zoneSpecification: sentryZoneSpecification,
  );
less 复制代码
///FlutterErrorIntegration.dart
@override
void call(Hub hub, SentryFlutterOptions options) {
  _defaultOnError = FlutterError.onError;
  _integrationOnError = (FlutterErrorDetails errorDetails) async {

      ......

      var event = SentryEvent(
        throwable: throwableMechanism,
        level: options.markAutomaticallyCollectedErrorsAsFatal
            ? SentryLevel.fatal
: SentryLevel.error,
        contexts: flutterErrorDetails.isNotEmpty
            ? (Contexts()..['flutter_error_details'] = flutterErrorDetails)
            : null,
        // ignore: invalid_use_of_internal_member
        timestamp: options.clock(),
      );

      await hub.captureEvent(event, stackTrace: stackTrace, hint: hint);
      // we don't call Zone.current.handleUncaughtError because we'd like
      // to set a specific mechanism for FlutterError.onError.
    }
    if (_defaultOnError != null) {
      _defaultOnError!(errorDetails);
    }
  };
  FlutterError.onError = _integrationOnError;

  options.sdk.addIntegration('flutterErrorIntegration');
}
相关推荐
数据知道2 小时前
claw-code 源码详细分析:Bootstrap Graph——启动阶段图式化之后,排障与扩展为什么会变简单?
前端·算法·ai·bootstrap·claude code·claw code
悟空瞎说2 小时前
深度解析:Vue3 为何弃用 defineProperty,Proxy 到底强在哪里?
前端·javascript
leafyyuki2 小时前
告别 Vuex 的繁琐!Pinia 如何以更优雅的方式重塑 Vue 状态管理
前端·javascript·vue.js
Amos_Web2 小时前
Solana开发(1)- 核心概念扫盲篇&&扫雷篇
前端·rust·区块链
Hooray2 小时前
AI 时代的管理后台框架,应该是什么样子?
前端·vue.js·ai编程
ZC跨境爬虫2 小时前
极验滑动验证码自动化实战(ddddocr免费方案):本地缺口识别与Playwright滑动模拟
前端·爬虫·python·自动化
某人辛木2 小时前
nodejs下载安装
开发语言·前端·javascript
独特的螺狮粉2 小时前
Flutter 框架跨平台鸿蒙开发 - 睡眠白噪音开发纪录
flutter·华为·harmonyos·鸿蒙
Ztopcloud极拓云视角3 小时前
Claude Code 源码泄露事件技术复盘:npm sourcemap 配置失误的完整分析
前端·npm·node.js