在程序运行过程中,可能会遇到各种错误或异常情况,例如网络连接失败、文件不存在、无效的用户输入等。如果不对这些异常进行妥善处理,程序可能会崩溃。Dart 提供了健壮的异常处理机制,允许我们捕获和响应这些运行时错误,从而使程序更加稳定和健壮。
Dart 中的异常是表示程序运行时发生意外情况的对象。与许多其他语言不同,Dart 中的所有异常都是非检查异常(Unchecked Exceptions),这意味着编译器不会强制你捕获它们。然而,为了程序的健壮性,仍然强烈建议捕获和处理可能发生的异常。
1. try-on-catch-finally 语句
Dart 使用 try、on、catch 和 finally 关键字来处理异常:
try:包含可能抛出异常的代码块。on:用于指定要捕获的特定异常类型。如果 try 块中抛出的异常类型与 on 后面指定的类型匹配,则执行 on 块中的代码。catch:用于捕获所有类型的异常,或者在 on 之后捕获特定类型的异常。catch 块可以接收两个参数:第一个是捕获到的异常对象,第二个是堆栈跟踪信息。finally:无论 try 块中是否发生异常,finally 块中的代码都会被执行。它通常用于执行清理操作,例如关闭文件或释放资源。void divide(int a, int b) {
try {
print("结果: ${a ~/ b}"); // 可能会抛出 IntegerDivisionByZeroException
} on IntegerDivisionByZeroException {
print("错误: 除数不能为零!");
} on FormatException catch (e) {
print("格式错误: $e");
} catch (e, s) { // 捕获所有其他异常,并获取堆栈信息
print("发生未知错误: $e");
print("堆栈跟踪: $s");
} finally {
print("除法操作完成。");
}
}
void main() {
divide(10, 2); // 正常执行
print("\n");
divide(10, 0); // 捕获 IntegerDivisionByZeroException
print("\n");
// 模拟其他异常
try {
throw Exception("这是一个自定义异常");
} catch (e) {
print("捕获到自定义异常: $e");
}
}
// 预期输出:
// 结果: 5
// 除法操作完成。
// 错误: 除数不能为零!
// 除法操作完成。
// 捕获到自定义异常: Exception: 这是一个自定义异常
2. throw 关键字
可以使用 throw 关键字显式地抛出异常。Dart 中的任何对象都可以作为异常被抛出,但通常建议抛出实现了 Error 或 Exception 类的对象。
void checkAge(int age) {
if (age < 0) {
throw ArgumentError("年龄不能为负数");
} else if (age > 150) {
throw Exception("年龄不能超过150岁");
}
print("年龄有效: $age");
}
void main() {
try {
checkAge(30);
checkAge(-5);
} on ArgumentError catch (e) {
print("捕获到参数错误: ${e.message}");
} catch (e) {
print("捕获到其他错误: $e");
}
}
// 预期输出:
// 年龄有效: 30
// 捕获到参数错误: 年龄不能为负数
3. 自定义异常
可以创建自定义异常类,通过继承 Exception 类来实现。这有助于更好地组织和区分不同类型的错误。
class MyCustomException implements Exception {
final String message;
MyCustomException(this.message);
@override
String toString() => "MyCustomException: $message";
}
void performRiskyOperation(int value) {
if (value < 0) {
throw MyCustomException("输入值不能为负数");
} else if (value == 0) {
throw StateError("状态错误:输入值为零");
}
print("操作成功,值为: $value");
}
void main() {
try {
performRiskyOperation(10);
performRiskyOperation(-1);
} on MyCustomException catch (e) {
print("捕获到自定义异常: ${e.message}");
} on StateError catch (e) {
print("捕获到状态错误: ${e.message}");
} catch (e) {
print("捕获到未知异常: $e");
}
}
// 预期输出:
// 操作成功,值为: 10
// 捕获到自定义异常: 输入值不能为负数
在 Flutter 应用中,异常处理是确保应用稳定性和用户体验的关键。无论是网络请求失败、文件读写错误,还是用户输入不合法,都需要进行适当的异常处理,以避免应用崩溃并向用户提供有用的反馈。
案例:用户登录表单的异常处理
我们将创建一个简单的用户登录表单,演示如何在处理用户输入和模拟网络请求时进行异常处理,并向用户显示相应的错误信息。
import 'package:flutter/material.dart';
// 自定义登录异常
class LoginException implements Exception {
final String message;
LoginException(this.message);
@override
String toString() => 'LoginException: $message';
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
Future<void> _login() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
final String username = _usernameController.text;
final String password = _passwordController.text;
try {
// 模拟网络请求延迟
await Future.delayed(const Duration(seconds: 2));
// 模拟登录逻辑和异常情况
if (username.isEmpty || password.isEmpty) {
throw LoginException('用户名或密码不能为空');
} else if (username == 'test' && password == 'password') {
// 登录成功
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('登录成功!')),
);
// 导航到主页或其他页面
} else if (username == 'error' && password == 'error') {
throw Exception('模拟服务器内部错误');
} else {
throw LoginException('用户名或密码不正确');
}
} on LoginException catch (e) {
// 捕获自定义登录异常
setState(() {
_errorMessage = e.message;
});
} catch (e) {
// 捕获其他未知异常
setState(() {
_errorMessage = '发生未知错误: ${e.toString()}';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('用户登录'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: '用户名',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16.0),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: '密码',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 24.0),
if (_errorMessage != null)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(
_errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 16.0),
),
),
ElevatedButton(
onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50), // 按钮宽度填充,高度50
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white) // 加载时显示进度指示器
: const Text(
'登录',
style: TextStyle(fontSize: 18.0),
),
),
],
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: LoginScreen()));
}
案例分析:
class LoginException implements Exception:我们定义了一个自定义异常 LoginException,用于表示登录相关的错误。这使得我们可以更精确地捕获和处理特定类型的错误。Future<void> _login() async { ... }:登录逻辑被封装在一个 async 函数中,以便进行异步操作(模拟网络请求)。try { ... } on LoginException catch (e) { ... } catch (e) { ... } finally { ... }:try 块包含了可能抛出异常的代码,例如用户输入验证和模拟的登录请求。on LoginException catch (e) 专门捕获 LoginException 类型的异常,并将其错误信息显示给用户。catch (e) 捕获所有其他类型的异常,作为通用错误处理。finally 块确保无论是否发生异常,_isLoading 状态都会被重置为 false,从而解除按钮的禁用状态。throw LoginException('用户名或密码不能为空');:在验证用户输入时,如果发现用户名或密码为空,我们显式地抛出 LoginException。这是一种主动报告错误的方式。throw Exception('模拟服务器内部错误');:为了演示通用异常的捕获,我们模拟了一个服务器内部错误,抛出了一个普通的 Exception。_errorMessage != null:在 UI 中,我们根据 _errorMessage 是否为 null 来决定是否显示错误信息。这是一种常见的错误反馈机制。_isLoading ? null : _login:当 _isLoading 为 true 时,登录按钮的 onPressed 回调被设置为 null,从而禁用按钮,防止用户重复点击。同时,按钮的文本会变为 CircularProgressIndicator,提供视觉反馈。这个案例清晰地展示了 Dart 的异常处理机制在 Flutter 应用中的实际应用。通过合理地使用 try-on-catch-finally 和 throw 关键字,我们可以构建出更加健壮、用户友好的应用程序,即使在面对各种运行时错误时也能保持稳定。