在 Flutter 中,合理使用 try-catch
和错误处理机制能有效定位问题并防止应用崩溃。以下是结合同步/异步代码、错误类型分类和全局捕获的完整实践方案:
一、同步代码的 try-catch
1. 基础语法
try {
// 可能抛出异常的同步代码
final result = int.parse('invalid_number');
} on FormatException catch (e) {
// 捕获特定异常类型
print('格式错误: $e');
} catch (e, s) {
// 捕获所有其他异常,s 是堆栈跟踪
print('未知错误: $e\n堆栈跟踪: $s');
} finally {
// 无论是否异常都会执行
print('清理资源');
}
2. 典型场景
- 数据转换:解析 JSON、字符串转数字。
- 文件操作:读取本地文件。
- 条件检查:断言失败(
assert
)。
二、异步代码的 try-catch
1. async/await
+ try-catch
Future<void> fetchData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode != 200) {
throw Exception('请求失败: ${response.statusCode}');
}
final data = jsonDecode(response.body);
} on SocketException {
// 处理网络错误
print('网络连接失败');
} on TimeoutException {
// 处理超时
print('请求超时');
} catch (e) {
// 其他错误
print('未知错误: $e');
}
}
2. Future.catchError
适用于链式调用:
http.get(url)
.then((response) => handleResponse(response))
.catchError((e) => print('请求失败: $e'));
三、错误定位与日志记录
1. 记录堆栈跟踪(Stack Trace)
try {
// ...
} catch (e, s) {
// s 是堆栈跟踪对象
log('错误: $e', stackTrace: s);
// 上报到 Sentry/Firebase Crashlytics
Sentry.captureException(e, stackTrace: s);
}
2. 使用 logger
库格式化日志
dependencies:
logger: ^2.0.0
final logger = Logger();
try {
// ...
} catch (e, s) {
logger.e('错误发生', error: e, stackTrace: s);
}
四、全局错误捕获
1. Flutter 框架层错误
void main() {
// 捕获 Widget 构建错误
FlutterError.onError = (FlutterErrorDetails details) {
logger.e('Flutter 错误', error: details.exception, stackTrace: details.stack);
// 上报到监控平台
FirebaseCrashlytics.instance.recordFlutterError(details);
};
// 捕获所有未处理的异常(包括异步)
PlatformDispatcher.instance.onError = (error, stack) {
logger.e('全局未捕获错误', error: error, stackTrace: stack);
return true; // 阻止默认错误处理
};
runApp(MyApp());
}
2. Dart 层全局捕获(仅调试环境)
void main() {
// 仅开发环境启用
if (kDebugMode) {
ErrorWidget.builder = (FlutterErrorDetails details) {
return Scaffold(
body: ErrorDetailsView(details), // 自定义错误页面
);
};
}
runApp(MyApp());
}
五、UI 层友好提示
1. 使用 FutureBuilder
展示错误
FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return ErrorWidget(snapshot.error!); // 自定义错误组件
} else if (snapshot.connectionState == ConnectionState.done) {
return DataView(snapshot.data!);
}
return CircularProgressIndicator();
},
);
2. 弹窗提示
try {
await fetchData();
} catch (e) {
if (!context.mounted) return; // 防止页面已销毁
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('操作失败'),
content: Text('错误原因: ${e.toString()}'),
),
);
}
六、最佳实践总结
场景 | 处理方式 | 工具/组件 |
---|---|---|
同步代码错误 | try-catch + 类型匹配 | on FormatException |
异步请求错误 | async/await + try-catch | Future.catchError |
全局未捕获异常 | FlutterError.onError | Firebase Crashlytics/Sentry |
用户界面反馈 | FutureBuilder + 弹窗 | showDialog /SnackBar |
错误日志记录 | Logger 库 + 堆栈跟踪 | logger /sentry |
开发调试辅助 | 自定义错误页面 | ErrorWidget.builder |
七、常见问题与解决
-
try-catch
无法捕获异步错误- 确保异步操作使用
await
,否则异常会进入 Future 的未捕获回调。
- 确保异步操作使用
-
页面销毁后调用
setState
- 在
catch
块中检查if (mounted)
:try { await fetchData(); } catch (e) { if (!mounted) return; setState(() => _error = e); }
- 在
-
错误信息不明确
- 使用
toString()
或自定义异常类(如ApiException
)提供清晰错误描述:class ApiException implements Exception { final String message; final int statusCode; ApiException(this.message, this.statusCode); String toString() => 'API错误 ($statusCode): $message'; }
- 使用
通过合理使用 try-catch
、全局错误捕获和用户反馈机制,可以显著提升 Flutter 应用的健壮性和用户体验,避免因未处理异常导致的崩溃。