错误捕获处理 - 鸿蒙Flutter健壮网络请求场景
·



概述
网络请求是应用中最容易出错的环节之一。网络不稳定、服务器故障、参数错误等都可能导致请求失败。一个健壮的应用必须能够全面捕获和处理各种错误情况,为用户提供良好的体验。
本章将详细介绍Flutter网络请求中的错误捕获与处理方法,包括错误类型分类、基础错误处理、分类错误处理、自定义异常类以及统一错误处理策略。
1. 错误类型分类
网络请求中可能遇到的错误可以分为以下几类:
| 错误类型 | 说明 | 处理方式 |
|---|---|---|
| SocketException | 网络连接失败,无法连接到服务器 | 检查网络状态,提示用户 |
| HttpException | HTTP协议错误,URL格式不正确等 | 检查URL格式 |
| FormatException | 数据格式错误,JSON解析失败等 | 检查响应格式,验证数据 |
| TimeoutException | 请求超时,服务器未在规定时间内响应 | 设置超时时间,实现重试 |
| 4xx状态码 | 客户端错误,请求参数错误或未授权 | 检查请求参数,提示用户 |
| 5xx状态码 | 服务器错误,服务器内部故障 | 重试请求或提示用户稍后重试 |
1.1 错误分类流程图
网络请求错误分类
├── 异常错误(Exception)
│ ├── SocketException: 网络连接失败
│ ├── HttpException: HTTP协议错误
│ ├── FormatException: 数据格式错误
│ └── TimeoutException: 请求超时
└── 状态码错误(StatusCode)
├── 4xx: 客户端错误
│ ├── 400: 请求参数错误
│ ├── 401: 未授权
│ ├── 403: 禁止访问
│ └── 404: 资源不存在
└── 5xx: 服务器错误
├── 500: 服务器内部错误
├── 502: 网关错误
└── 503: 服务不可用
2. 基础错误处理
基础错误处理使用try-catch包裹网络请求,捕获所有可能的异常。
2.1 最简单的错误处理
import 'package:http/http.dart' as http;
Future<void> basicErrorHandling() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
print('请求成功');
} else {
print('服务器返回错误: ${response.statusCode}');
}
} catch (e) {
print('网络请求失败: $e');
}
}
2.2 基础错误处理的问题
这种方式虽然简单,但存在以下问题:
- 无法区分错误类型:所有异常都被同一个catch块捕获
- 错误信息不明确:用户无法知道具体是什么错误
- 处理方式单一:无法根据不同错误类型采取不同策略
3. 分类错误处理
分类错误处理针对不同的异常类型和状态码进行差异化处理。
3.1 按异常类型捕获
import 'dart:io';
Future<void> detailedErrorHandling() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode >= 200 && response.statusCode < 300) {
print('请求成功');
} else if (response.statusCode == 400) {
print('请求参数错误');
} else if (response.statusCode == 401) {
print('未授权,请重新登录');
} else if (response.statusCode == 403) {
print('禁止访问');
} else if (response.statusCode == 404) {
print('资源不存在');
} else if (response.statusCode >= 500) {
print('服务器错误,请稍后重试');
}
} on SocketException {
print('网络连接失败,请检查网络');
} on HttpException {
print('HTTP协议错误');
} on FormatException {
print('数据格式错误');
} catch (e) {
print('未知错误: $e');
}
}
3.2 分类处理的优势
- 精确识别错误类型:可以知道是网络问题、协议问题还是数据格式问题
- 针对性处理:不同错误类型采取不同的处理策略
- 友好提示:为用户提供更准确的错误信息
4. 自定义异常类
为了更好地管理错误,我们可以定义自定义异常类。
4.1 错误类型枚举
enum ApiErrorType {
network, // 网络错误
unauthorized, // 未授权
forbidden, // 禁止访问
notFound, // 资源不存在
server, // 服务器错误
timeout, // 请求超时
unknown, // 未知错误
}
4.2 自定义异常类
class ApiException implements Exception {
final ApiErrorType type;
final String message;
final int? statusCode;
ApiException({
required this.type,
required this.message,
this.statusCode,
});
String toString() {
return 'ApiException(type: $type, message: $message, statusCode: $statusCode)';
}
}
4.3 使用自定义异常
Future<void> fetchData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode >= 200 && response.statusCode < 300) {
print('请求成功');
} else {
throw _createApiException(response.statusCode);
}
} on SocketException {
throw ApiException(
type: ApiErrorType.network,
message: '网络连接失败',
);
} catch (e) {
if (e is ApiException) {
print('API错误: ${e.type}, ${e.message}');
} else {
throw ApiException(
type: ApiErrorType.unknown,
message: e.toString(),
);
}
}
}
ApiException _createApiException(int statusCode) {
switch (statusCode) {
case 401:
return ApiException(
type: ApiErrorType.unauthorized,
message: '未授权,请重新登录',
statusCode: statusCode,
);
case 403:
return ApiException(
type: ApiErrorType.forbidden,
message: '禁止访问',
statusCode: statusCode,
);
case 404:
return ApiException(
type: ApiErrorType.notFound,
message: '资源不存在',
statusCode: statusCode,
);
case >= 500:
return ApiException(
type: ApiErrorType.server,
message: '服务器错误,请稍后重试',
statusCode: statusCode,
);
default:
return ApiException(
type: ApiErrorType.unknown,
message: '未知错误',
statusCode: statusCode,
);
}
}
5. 统一错误处理
统一错误处理将错误处理逻辑集中到一个函数中,避免代码重复。
5.1 统一错误处理函数
void handleApiError(dynamic error) {
if (error is ApiException) {
switch (error.type) {
case ApiErrorType.network:
print('网络错误: ${error.message}');
// 显示网络错误提示
break;
case ApiErrorType.unauthorized:
print('未授权: ${error.message}');
// 跳转到登录页面
break;
case ApiErrorType.forbidden:
print('禁止访问: ${error.message}');
// 显示权限不足提示
break;
case ApiErrorType.notFound:
print('资源不存在: ${error.message}');
// 显示404页面
break;
case ApiErrorType.server:
print('服务器错误: ${error.message}');
// 显示重试按钮
break;
case ApiErrorType.timeout:
print('请求超时: ${error.message}');
// 显示超时提示,提供重试
break;
case ApiErrorType.unknown:
print('未知错误: ${error.message}');
// 显示通用错误提示
break;
}
} else {
print('错误: $error');
// 显示通用错误提示
}
}
5.2 在UI中使用统一错误处理
Future<void> fetchData() async {
setState(() => _loading = true);
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
// 处理成功数据
} else {
throw _createApiException(response.statusCode);
}
} catch (e) {
handleApiError(e);
} finally {
setState(() => _loading = false);
}
}
6. 错误日志记录
错误日志对于排查问题至关重要,应该记录详细的错误信息。
6.1 日志记录函数
void logError({
required String url,
required dynamic error,
StackTrace? stackTrace,
Map<String, dynamic>? requestData,
}) {
String timestamp = DateTime.now().toIso8601String();
StringBuffer logBuffer = StringBuffer();
logBuffer.writeln('[$timestamp] URL: $url');
logBuffer.writeln('Error: $error');
if (requestData != null) {
logBuffer.writeln('Request Data: $requestData');
}
if (stackTrace != null) {
logBuffer.writeln('StackTrace: $stackTrace');
}
print(logBuffer.toString());
// 在生产环境中,可以发送到日志服务器
// await _sendToLogServer(logBuffer.toString());
}
6.2 使用日志记录
Future<void> fetchData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode != 200) {
logError(
url: 'https://api.example.com/data',
error: 'HTTP Error: ${response.statusCode}',
);
}
} catch (e, stackTrace) {
logError(
url: 'https://api.example.com/data',
error: e,
stackTrace: stackTrace,
);
}
}
7. 错误处理最佳实践
7.1 使用try-catch包裹所有网络请求
// 错误做法
final response = await http.get(uri);
var data = jsonDecode(response.body);
// 正确做法
try {
final response = await http.get(uri);
var data = jsonDecode(response.body);
} catch (e) {
// 处理错误
}
7.2 区分网络错误和业务错误
网络错误和业务错误需要不同的处理方式:
- 网络错误:提示用户检查网络连接
- 业务错误:根据具体情况提示用户(如参数错误、权限不足等)
7.3 对不同状态码进行分类处理
不同的状态码代表不同的错误类型,应该进行分类处理:
- 401:跳转到登录页面
- 403:显示权限不足提示
- 404:显示资源不存在页面
- 5xx:显示服务器错误提示,提供重试按钮
7.4 使用自定义异常类统一错误类型
自定义异常类可以使错误处理更加清晰和统一。
7.5 记录详细的错误日志
在开发阶段和生产环境中都应该记录详细的错误日志,便于问题排查。
7.6 为用户提供友好的错误提示
错误提示应该简洁明了,让用户知道发生了什么问题以及如何解决。
7.7 考虑实现请求重试机制
对于网络不稳定导致的错误,可以实现重试机制,提高请求成功率。
8. 实践示例:完整的错误处理
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
enum ApiErrorType {
network,
unauthorized,
forbidden,
notFound,
server,
timeout,
unknown,
}
class ApiException implements Exception {
final ApiErrorType type;
final String message;
final int? statusCode;
ApiException({
required this.type,
required this.message,
this.statusCode,
});
}
class ApiClient {
Future<dynamic> get(String url) async {
try {
final response = await http.get(Uri.parse(url));
return _handleResponse(response);
} on SocketException {
throw ApiException(
type: ApiErrorType.network,
message: '网络连接失败,请检查网络',
);
} on FormatException {
throw ApiException(
type: ApiErrorType.unknown,
message: '数据格式错误',
);
} catch (e) {
throw ApiException(
type: ApiErrorType.unknown,
message: e.toString(),
);
}
}
dynamic _handleResponse(http.Response response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
return jsonDecode(response.body);
} else {
throw _createApiException(response.statusCode);
}
}
ApiException _createApiException(int statusCode) {
switch (statusCode) {
case 401:
return ApiException(
type: ApiErrorType.unauthorized,
message: '未授权,请重新登录',
statusCode: statusCode,
);
case 403:
return ApiException(
type: ApiErrorType.forbidden,
message: '禁止访问',
statusCode: statusCode,
);
case 404:
return ApiException(
type: ApiErrorType.notFound,
message: '资源不存在',
statusCode: statusCode,
);
case >= 500:
return ApiException(
type: ApiErrorType.server,
message: '服务器错误,请稍后重试',
statusCode: statusCode,
);
default:
return ApiException(
type: ApiErrorType.unknown,
message: '未知错误',
statusCode: statusCode,
);
}
}
}
void handleApiError(ApiException error) {
switch (error.type) {
case ApiErrorType.network:
print('显示网络错误提示');
break;
case ApiErrorType.unauthorized:
print('跳转到登录页面');
break;
case ApiErrorType.server:
print('显示重试按钮');
break;
default:
print('显示通用错误提示: ${error.message}');
}
}
9. 错误处理的UI展示
在Flutter中,可以通过以下方式展示错误信息:
9.1 使用SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error.message),
backgroundColor: Colors.red,
),
);
9.2 使用Dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('错误'),
content: Text(error.message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
9.3 使用ErrorWidget
if (hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 48),
const SizedBox(height: 16),
Text(error.message),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => retry(),
child: const Text('重试'),
),
],
),
);
}
10. 总结
错误处理是网络编程中不可或缺的一部分:
- 分类处理:区分网络错误、协议错误、数据格式错误和状态码错误
- 自定义异常:使用自定义异常类统一错误类型
- 统一处理:将错误处理逻辑集中到一个函数中
- 日志记录:记录详细的错误信息便于排查
- 用户友好:为用户提供清晰的错误提示和解决方案
一个健壮的应用必须能够优雅地处理各种错误情况,为用户提供良好的体验。
参考资源
更多推荐



所有评论(0)