攻克Dio网络异常:从原理剖析到企业级解决方案
网络异常处理是移动应用开发中的关键环节,直接影响用户体验和应用稳定性。作为Flutter生态中最流行的网络请求库,Dio提供了强大的异常处理机制,但开发者常常在实际应用中遇到各类问题。本文将系统分析Dio异常处理的底层原理,提供从基础到进阶的完整解决方案,并分享企业级应用中的最佳实践。
问题诊断:网络异常的表现与影响
在移动应用开发中,网络异常是导致用户投诉的主要原因之一。典型的网络问题表现为:页面加载无限期阻塞、操作后无响应、数据展示不完整或应用崩溃。这些问题背后隐藏着不同类型的网络异常,需要针对性处理。
常见异常场景分析
案例1:用户反馈"列表刷新半天没反应"
- 可能原因:连接超时或网络不稳定
- 技术本质:DioExceptionType.connectionTimeout或connectionError
- 影响范围:所有依赖网络的功能模块
案例2:支付后订单状态不更新
- 可能原因:请求已发送但未收到响应
- 技术本质:DioExceptionType.receiveTimeout或cancel
- 业务风险:可能导致用户重复支付或订单状态不一致
案例3:应用在特定网络环境下频繁闪退
- 可能原因:未捕获的异常导致应用崩溃
- 技术本质:未处理的DioException异常
- 严重程度:高,直接影响应用可用性
异常影响量化分析
根据行业统计数据,网络异常导致:
- 应用留存率下降20-30%
- 用户满意度降低40%
- 转化率降低15-25%
有效的异常处理不仅能提升用户体验,还能直接带来业务指标的改善。
原理剖析:Dio异常处理机制
要构建健壮的异常处理系统,首先需要深入理解Dio的异常处理底层原理。Dio将所有网络相关错误统一封装为DioException对象,形成了完整的异常处理体系。
Dio异常体系架构
Dio异常体系基于面向对象设计,核心类层次结构如下:
Exception
└── DioException
├── RequestOptions requestOptions // 请求配置信息
├── Response? response // 响应数据(可能为null)
├── DioExceptionType type // 异常类型枚举
├── Object? error // 原始错误对象
└── String? message // 错误描述信息
Dio定义了8种标准异常类型,每种类型对应特定的错误场景:
| 异常类型 | 触发条件 | 底层原因 |
|---|---|---|
| connectionTimeout | 建立连接超时 | TCP握手未在指定时间内完成 |
| sendTimeout | 发送数据超时 | 数据发送过程超出时间限制 |
| receiveTimeout | 接收数据超时 | 未在指定时间内收到响应 |
| badCertificate | 证书验证失败 | SSL/TLS证书验证过程失败 |
| badResponse | 非预期状态码 | 服务器返回4xx或5xx状态码 |
| cancel | 请求被主动取消 | 通过CancelToken触发取消操作 |
| connectionError | 网络连接错误 | 网络不可用或DNS解析失败 |
| unknown | 未知错误 | 无法归类的异常情况 |
异常传播机制
Dio异常通过两种路径传播:
- 直接抛出:通过
try/catch捕获的同步异常 - 回调传递:通过拦截器链传递的异步异常
拦截器中的异常处理流程如下:
// 简化的Dio拦截器异常处理流程
class Interceptor {
Future onRequest(RequestOptions options) async => options;
Future onResponse(Response response) async => response;
Future onError(DioException err) async => err;
}
// 异常传播路径
Request → RequestInterceptor → Adapter → ResponseInterceptor → Response
↘ ErrorInterceptorัฒ
当发生异常时,Dio会中断正常流程,将异常传递给错误拦截器进行处理。理解这一机制对于实现全局异常处理至关重要。
解决方案:构建完整异常处理体系
基于Dio的异常机制,我们可以构建从基础到高级的完整异常处理方案。以下是经过实践验证的分层次解决方案。
1. 基础异常捕获与处理
同步捕获机制:使用try/catch捕获特定异常类型
/// 基础网络请求封装
class ApiClient {
final Dio _dio;
ApiClient() : _dio = Dio() {
_setupDio();
}
void _setupDio() {
_dio.options.baseUrl = 'https://api.example.com';
_dio.options.connectTimeout = Duration(seconds: 5);
_dio.options.receiveTimeout = Duration(seconds: 3);
}
/// GET请求基础实现
Future<T> get<T>(String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.get(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return _handleResponse<T>(response);
} on DioException catch (e) {
// 捕获并处理Dio特定异常
return _handleDioException<T>(e);
} catch (e) {
// 捕获其他类型异常
throw ApiException(
message: '未知错误: ${e.toString()}',
type: ApiExceptionType.unknown,
);
}
}
// 响应处理逻辑
T _handleResponse<T>(Response response) {
// 处理正常响应
if (response.statusCode != null && response.statusCode! >= 200 && response.statusCode! < 300) {
return response.data as T;
}
// 处理非成功状态码
throw ApiException(
message: '请求失败: ${response.statusCode}',
type: ApiExceptionType.httpError,
statusCode: response.statusCode,
);
}
// Dio异常处理逻辑
T _handleDioException<T>(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
throw ApiException(
message: '网络连接超时,请检查网络状态',
type: ApiExceptionType.connectionTimeout,
originalException: e,
);
case DioExceptionType.sendTimeout:
throw ApiException(
message: '发送数据超时,请稍后重试',
type: ApiExceptionType.sendTimeout,
originalException: e,
);
// 其他异常类型处理...
default:
throw ApiException(
message: '网络请求失败: ${e.message}',
type: ApiExceptionType.generic,
originalException: e,
);
}
}
}
/// 应用级异常封装
class ApiException implements Exception {
final String message;
final ApiExceptionType type;
final int? statusCode;
final DioException? originalException;
ApiException({
required this.message,
required this.type,
this.statusCode,
this.originalException,
});
@override
String toString() => 'ApiException($type): $message';
}
/// 应用异常类型枚举
enum ApiExceptionType {
connectionTimeout,
sendTimeout,
receiveTimeout,
badCertificate,
badResponse,
cancel,
connectionError,
unknown,
httpError,
generic,
}
关键设计点:
- 定义应用级异常类型,隔离Dio实现细节
- 统一异常处理入口,便于集中管理
- 保留原始异常信息,便于问题排查
2. 全局异常处理拦截器
通过Dio拦截器实现全局异常统一处理:
/// 全局异常处理拦截器
class GlobalErrorInterceptor extends Interceptor {
final ErrorReporter _errorReporter;
final NetworkConfig _networkConfig;
GlobalErrorInterceptor(this._errorReporter, this._networkConfig);
@override
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
// 1. 记录异常日志
_logError(err);
// 2. 上报异常信息
if (_networkConfig.shouldReportError(err)) {
await _reportError(err);
}
// 3. 处理特定异常场景
if (err.type == DioExceptionType.connectionError && await _isNetworkAvailable()) {
// 网络可用但连接失败,可能是服务器问题
handler.resolve(_createFallbackResponse(err.requestOptions));
return;
}
// 4. 决定是否继续传播异常
if (_shouldPropagateError(err)) {
handler.next(err);
} else {
// 某些场景下可以消化异常,返回默认响应
handler.resolve(_createEmptyResponse(err.requestOptions));
}
}
// 日志记录
void _logError(DioException err) {
debugPrint('''
Dio Error:
Type: ${err.type}
Message: ${err.message}
URL: ${err.requestOptions.uri}
Status Code: ${err.response?.statusCode}
Stack Trace: ${err.stackTrace}
''');
}
// 异常上报
Future<void> _reportError(DioException err) async {
try {
await _errorReporter.report({
'type': err.type.toString(),
'message': err.message,
'url': err.requestOptions.uri.toString(),
'method': err.requestOptions.method,
'statusCode': err.response?.statusCode,
'timestamp': DateTime.now().toIso8601String(),
'deviceInfo': await DeviceInfo.getBasicInfo(),
});
} catch (e) {
debugPrint('Failed to report error: $e');
}
}
// 检查网络可用性
Future<bool> _isNetworkAvailable() async {
// 实现网络检查逻辑
return true;
}
// 创建降级响应
Response _createFallbackResponse(RequestOptions options) {
return Response(
requestOptions: options,
statusCode: 200,
data: {'source': 'fallback', 'data': {}},
);
}
// 创建空响应
Response _createEmptyResponse(RequestOptions options) {
return Response(
requestOptions: options,
statusCode: 200,
data: {},
);
}
// 判断是否需要传播异常
bool _shouldPropagateError(DioException err) {
// 根据异常类型决定是否传播
return ![DioExceptionType.cancel].contains(err.type);
}
}
// 注册拦截器
void setupDioInterceptors(Dio dio) {
dio.interceptors.add(GlobalErrorInterceptor(
ErrorReporter(),
NetworkConfig(),
));
// 添加其他拦截器
dio.interceptors.add(LogInterceptor(responseBody: true));
}
拦截器设计要点:
- 职责分离:日志记录、错误上报、异常处理分离
- 条件处理:根据异常类型和网络状况决定处理策略
- 降级机制:在特定情况下返回兜底数据,避免应用崩溃
3. 请求重试与熔断机制
实现智能重试和熔断机制,提高系统稳定性:
/// 带重试和熔断功能的请求封装
class ResilientRequestHandler {
final Dio _dio;
final CircuitBreaker _circuitBreaker;
ResilientRequestHandler(this._dio) : _circuitBreaker = CircuitBreaker();
/// 带重试和熔断的请求方法
Future<T> executeWithResilience<T>({
required String method,
required String path,
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
int maxRetries = 2,
Duration initialDelay = const Duration(milliseconds: 300),
}) async {
// 检查熔断器状态
if (!_circuitBreaker.allowRequest()) {
throw ApiException(
message: '服务暂时不可用,请稍后再试',
type: ApiExceptionType.circuitOpen,
);
}
int attempts = 0;
late DioException lastException;
while (attempts <= maxRetries) {
try {
final response = await _dio.request(
path,
method: method,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
// 请求成功,重置熔断器状态
_circuitBreaker.onSuccess();
return response.data as T;
} on DioException catch (e) {
lastException = e;
attempts++;
// 判断是否应该重试
if (!_shouldRetry(e, attempts, maxRetries)) {
break;
}
// 指数退避策略
final delay = initialDelay * (1 << (attempts - 1));
debugPrint('请求失败,将在${delay.inMilliseconds}ms后重试,第$attempts次');
await Future.delayed(delay);
}
}
// 请求失败,通知熔断器
_circuitBreaker.onFailure();
throw _wrapException(lastException);
}
// 判断是否应该重试
bool _shouldRetry(DioException e, int attempts, int maxRetries) {
if (attempts > maxRetries) return false;
// 取消请求不重试
if (e.type == DioExceptionType.cancel) return false;
// 4xx客户端错误不重试
if (e.type == DioExceptionType.badResponse &&
e.response?.statusCode != null &&
e.response!.statusCode! >= 400 &&
e.response!.statusCode! < 500) {
return false;
}
// 以下类型异常可以重试
return [
DioExceptionType.connectionTimeout,
DioExceptionType.sendTimeout,
DioExceptionType.receiveTimeout,
DioExceptionType.connectionError,
// 5xx服务器错误可以重试
if (e.type == DioExceptionType.badResponse &&
e.response?.statusCode != null &&
e.response!.statusCode! >= 500)
DioExceptionType.badResponse,
].contains(e.type);
}
// 异常包装
ApiException _wrapException(DioException e) {
return ApiException(
message: e.message ?? '请求失败',
type: _mapDioExceptionType(e.type),
statusCode: e.response?.statusCode,
originalException: e,
);
}
// 映射Dio异常类型到应用异常类型
ApiExceptionType _mapDioExceptionType(DioExceptionType type) {
switch (type) {
case DioExceptionType.connectionTimeout:
return ApiExceptionType.connectionTimeout;
case DioExceptionType.sendTimeout:
return ApiExceptionType.sendTimeout;
case DioExceptionType.receiveTimeout:
return ApiExceptionType.receiveTimeout;
case DioExceptionType.badCertificate:
return ApiExceptionType.badCertificate;
case DioExceptionType.badResponse:
return ApiExceptionType.badResponse;
case DioExceptionType.cancel:
return ApiExceptionType.cancel;
case DioExceptionType.connectionError:
return ApiExceptionType.connectionError;
case DioExceptionType.unknown:
return ApiExceptionType.unknown;
}
}
}
/// 熔断器实现
class CircuitBreaker {
// 状态枚举
enum State { closed, open, halfOpen }
// 配置参数
final int failureThreshold;
final Duration resetTimeout;
final int successThreshold;
// 状态变量
State _state = State.closed;
int _failureCount = 0;
int _successCount = 0;
DateTime? _lastFailureTime;
CircuitBreaker({
this.failureThreshold = 5,
this.resetTimeout = const Duration(seconds: 30),
this.successThreshold = 2,
});
// 判断是否允许请求
bool allowRequest() {
switch (_state) {
case State.closed:
return true;
case State.open:
// 检查是否达到重置时间
if (_lastFailureTime != null &&
DateTime.now().difference(_lastFailureTime!) > resetTimeout) {
_state = State.halfOpen;
return true;
}
return false;
case State.halfOpen:
return true;
}
}
// 处理成功事件
void onSuccess() {
switch (_state) {
case State.closed:
_failureCount = 0;
break;
case State.halfOpen:
_successCount++;
if (_successCount >= successThreshold) {
_state = State.closed;
_failureCount = 0;
_successCount = 0;
}
break;
case State.open:
// 不应发生
break;
}
}
// 处理失败事件
void onFailure() {
switch (_state) {
case State.closed:
_failureCount++;
_lastFailureTime = DateTime.now();
if (_failureCount >= failureThreshold) {
_state = State.open;
}
break;
case State.halfOpen:
_state = State.open;
_failureCount = 1;
_lastFailureTime = DateTime.now();
break;
case State.open:
_failureCount++;
_lastFailureTime = DateTime.now();
break;
}
}
// 获取当前状态
State get state => _state;
}
熔断机制优势:
- 防止故障级联传播,保护后端服务
- 自动恢复机制,减少人工干预
- 提供稳定的用户体验,避免反复失败
进阶优化:构建企业级异常处理系统
企业级应用需要更完善的异常处理策略,包括缓存降级、离线支持和用户体验优化等方面。
1. 多级缓存与降级策略
实现请求结果缓存,在网络异常时提供缓存数据:
/// 缓存拦截器实现
class CacheInterceptor extends Interceptor {
final CacheManager _cacheManager;
CacheInterceptor(this._cacheManager);
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler
) async {
// 检查是否需要缓存
if (!_shouldCacheRequest(options)) {
return handler.next(options);
}
// 生成缓存键
final cacheKey = _generateCacheKey(options);
// 检查缓存是否存在且有效
final cacheData = await _cacheManager.getCache(cacheKey);
if (cacheData != null && !_isCacheExpired(cacheData, options)) {
// 返回缓存数据
return handler.resolve(Response(
requestOptions: options,
data: cacheData.data,
statusCode: 200,
extra: {'source': 'cache'},
));
}
// 缓存不存在或已过期,继续请求
handler.next(options);
}
@override
Future<void> onResponse(
Response response,
ResponseInterceptorHandler handler
) async {
// 检查是否需要缓存响应
if (_shouldCacheResponse(response)) {
final cacheKey = _generateCacheKey(response.requestOptions);
await _cacheManager.saveCache(
key: cacheKey,
data: response.data,
ttl: _getCacheTtl(response.requestOptions),
);
}
handler.next(response);
}
// 判断请求是否需要缓存
bool _shouldCacheRequest(RequestOptions options) {
// 只缓存GET请求
if (options.method.toUpperCase() != 'GET') return false;
// 检查是否有缓存策略
if (options.extra.containsKey('cachePolicy')) {
final policy = options.extra['cachePolicy'] as CachePolicy;
return policy != CachePolicy.noCache;
}
// 默认缓存GET请求
return true;
}
// 判断响应是否需要缓存
bool _shouldCacheResponse(Response response) {
// 只缓存成功响应
if (response.statusCode == null || response.statusCode! < 200 || response.statusCode! >= 300) {
return false;
}
return _shouldCacheRequest(response.requestOptions);
}
// 生成缓存键
String _generateCacheKey(RequestOptions options) {
// 基于URL和查询参数生成唯一键
final queryParams = options.queryParameters.entries
.map((e) => '${e.key}=${e.value}')
.toList()
..sort();
return '${options.uri.path}?${queryParams.join('&')}';
}
// 检查缓存是否过期
bool _isCacheExpired(CacheData cacheData, RequestOptions options) {
// 获取缓存策略
final policy = options.extra.containsKey('cachePolicy')
? options.extra['cachePolicy'] as CachePolicy
: CachePolicy.forceCache;
// 强制缓存策略下不过期
if (policy == CachePolicy.forceCache) return false;
// 检查缓存时间
final now = DateTime.now();
final cacheAge = now.difference(cacheData.timestamp);
// 获取TTL
final ttl = _getCacheTtl(options);
return cacheAge > ttl;
}
// 获取缓存TTL
Duration _getCacheTtl(RequestOptions options) {
// 优先使用请求中指定的TTL
if (options.extra.containsKey('cacheTtl')) {
return options.extra['cacheTtl'] as Duration;
}
// 默认TTL为5分钟
return Duration(minutes: 5);
}
}
/// 缓存策略枚举
enum CachePolicy {
noCache, // 不缓存
forceCache, // 强制使用缓存,不管是否过期
cacheFirst, // 优先使用缓存,如果没有则请求网络
networkFirst, // 优先请求网络,如果失败则使用缓存
}
/// 缓存数据模型
class CacheData {
final dynamic data;
final DateTime timestamp;
CacheData({required this.data, DateTime? timestamp})
: timestamp = timestamp ?? DateTime.now();
}
/// 缓存管理器接口
abstract class CacheManager {
Future<CacheData?> getCache(String key);
Future<void> saveCache({required String key, required dynamic data, required Duration ttl});
Future<void> clearCache();
Future<void> removeCache(String key);
}
/// 内存缓存实现
class MemoryCacheManager implements CacheManager {
final Map<String, CacheData> _cache = {};
final Map<String, Timer> _expirationTimers = {};
@override
Future<CacheData?> getCache(String key) async {
return _cache[key];
}
@override
Future<void> saveCache({
required String key,
required dynamic data,
required Duration ttl
}) async {
_cache[key] = CacheData(data: data);
// 设置过期定时器
_expirationTimers[key]?.cancel();
_expirationTimers[key] = Timer(ttl, () {
_cache.remove(key);
_expirationTimers.remove(key);
});
}
@override
Future<void> clearCache() async {
_cache.clear();
_expirationTimers.forEach((key, timer) => timer.cancel());
_expirationTimers.clear();
}
@override
Future<void> removeCache(String key) async {
_cache.remove(key);
_expirationTimers[key]?.cancel();
_expirationTimers.remove(key);
}
}
2. 离线数据支持系统
结合本地数据库实现完整的离线功能:
/// 离线数据仓库实现
class OfflineRepository<T> {
final Dio _dio;
final LocalDatabase<T> _localDb;
final NetworkInfo _networkInfo;
OfflineRepository(this._dio, this._localDb, this._networkInfo);
/// 获取数据 - 支持离线模式
Future<Result<List<T>>> getItems({
required String remotePath,
required String localTable,
bool forceRefresh = false,
Duration? staleDuration,
}) async {
// 检查网络状态
final isConnected = await _networkInfo.isConnected;
// 如果有网络且不需要强制刷新,检查本地数据是否足够新
if (isConnected && !forceRefresh) {
final lastUpdated = await _localDb.getLastUpdated(localTable);
// 如果本地数据存在且未过期,直接返回本地数据
if (lastUpdated != null &&
staleDuration != null &&
DateTime.now().difference(lastUpdated) < staleDuration) {
final localData = await _localDb.getItems(localTable);
return Result.success(localData, source: DataSource.local);
}
}
try {
// 有网络或需要强制刷新,从网络获取
final response = await _dio.get(remotePath);
final remoteData = (response.data as List)
.map((json) => _fromJson(json))
.toList();
// 保存到本地数据库
await _localDb.saveItems(localTable, remoteData);
return Result.success(remoteData, source: DataSource.remote);
} on DioException catch (e) {
// 网络请求失败
if (!isConnected) {
// 无网络,尝试返回本地数据
final localData = await _localDb.getItems(localTable);
if (localData.isNotEmpty) {
return Result.success(localData, source: DataSource.local);
} else {
return Result.failure(
ApiException(
message: '无网络连接且本地无缓存数据',
type: ApiExceptionType.offlineNoCache,
),
);
}
} else {
// 有网络但请求失败,尝试返回本地数据
final localData = await _localDb.getItems(localTable);
if (localData.isNotEmpty) {
return Result.success(localData, source: DataSource.local);
} else {
return Result.failure(
ApiException(
message: '请求失败且本地无缓存数据',
type: ApiExceptionType.networkError,
originalException: e,
),
);
}
}
}
}
// JSON转模型
T _fromJson(Map<String, dynamic> json);
}
/// 数据来源枚举
enum DataSource {
remote, // 远程服务器
local, // 本地缓存
}
/// 结果封装类
class Result<T> {
final T? data;
final Exception? error;
final DataSource? source;
// 成功构造函数
Result.success(this.data, {this.source}) : error = null;
// 失败构造函数
Result.failure(this.error) : data = null, source = null;
// 是否成功
bool get isSuccess => error == null;
// 是否失败
bool get isFailure => error != null;
}
/// 网络信息接口
abstract class NetworkInfo {
Future<bool> get isConnected;
}
/// 本地数据库接口
abstract class LocalDatabase<T> {
Future<List<T>> getItems(String table);
Future<void> saveItems(String table, List<T> items);
Future<DateTime?> getLastUpdated(String table);
Future<void> clearTable(String table);
}
3. 可视化异常监控系统
实现异常监控看板,实时跟踪网络请求状态:
/// 异常监控服务
class NetworkMonitorService {
final _requestHistory = <RequestRecord>[];
final _errorStats = <String, int>{};
final _requestStats = <String, RequestStats>{};
// 记录请求
void recordRequest(RequestOptions options) {
final record = RequestRecord(
url: options.uri.toString(),
method: options.method,
startTime: DateTime.now(),
);
_requestHistory.add(record);
// 更新统计
final key = '${options.method}:${options.uri.path}';
_requestStats[key] ??= RequestStats(path: options.uri.path, method: options.method);
_requestStats[key]!.totalRequests++;
}
// 记录响应
void recordResponse(Response response) {
final url = response.requestOptions.uri.toString();
final record = _requestHistory.lastWhere((r) => r.url == url, orElse: () => null);
if (record != null) {
record.endTime = DateTime.now();
record.statusCode = response.statusCode;
record.duration = record.endTime.difference(record.startTime);
// 更新统计
final key = '${response.requestOptions.method}:${response.requestOptions.uri.path}';
if (_requestStats.containsKey(key)) {
_requestStats[key]!.successRequests++;
_requestStats[key]!.totalDuration += record.duration;
}
}
}
// 记录错误
void recordError(DioException e) {
final url = e.requestOptions.uri.toString();
final record = _requestHistory.lastWhere((r) => r.url == url, orElse: () => null);
if (record != null) {
record.endTime = DateTime.now();
record.statusCode = e.response?.statusCode;
record.duration = record.endTime.difference(record.startTime);
record.error = e;
// 更新错误统计
final errorType = e.type.toString();
_errorStats[errorType] = (_errorStats[errorType] ?? 0) + 1;
// 更新请求统计
final key = '${e.requestOptions.method}:${e.requestOptions.uri.path}';
if (_requestStats.containsKey(key)) {
_requestStats[key]!.failedRequests++;
}
}
}
// 获取请求历史
List<RequestRecord> getRequestHistory() => List.unmodifiable(_requestHistory);
// 获取错误统计
Map<String, int> getErrorStats() => Map.unmodifiable(_errorStats);
// 获取请求统计
Map<String, RequestStats> getRequestStats() => Map.unmodifiable(_requestStats);
// 清除历史数据
void clearHistory() {
_requestHistory.clear();
_errorStats.clear();
_requestStats.clear();
}
}
/// 请求记录模型
class RequestRecord {
final String url;
final String method;
final DateTime startTime;
DateTime? endTime;
Duration? duration;
int? statusCode;
DioException? error;
RequestRecord({
required this.url,
required this.method,
required this.startTime,
});
}
/// 请求统计模型
class RequestStats {
final String path;
final String method;
int totalRequests = 0;
int successRequests = 0;
int failedRequests = 0;
Duration totalDuration = Duration.zero;
RequestStats({required this.path, required this.method});
// 成功率
double get successRate => totalRequests == 0
? 0
: successRequests / totalRequests;
// 平均响应时间
Duration get averageDuration => totalRequests == 0
? Duration.zero
: totalDuration ~/ totalRequests;
}
最佳实践与陷阱规避
异常处理最佳实践
-
分层异常处理
- 底层:Dio异常捕获与转换
- 中间层:业务异常处理与转换
- 上层:UI层错误展示与用户交互
-
异常信息标准化
- 定义统一的错误码体系
- 提供用户友好的错误消息
- 保留技术细节用于调试
-
请求取消机制
- 为每个请求创建CancelToken
- 在Widget销毁时取消请求
- 实现批量取消功能
// 页面级请求取消管理
class RequestCanceler {
final List<CancelToken> _cancelTokens = [];
// 创建新的CancelToken
CancelToken createCancelToken() {
final token = CancelToken();
_cancelTokens.add(token);
return token;
}
// 取消所有请求
void cancelAll() {
for (final token in _cancelTokens) {
if (!token.isCancelled) {
token.cancel('页面已关闭');
}
}
_cancelTokens.clear();
}
}
// Widget中使用
class MyPage extends StatefulWidget {
@override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
late RequestCanceler _requestCanceler;
@override
void initState() {
super.initState();
_requestCanceler = RequestCanceler();
_loadData();
}
Future<void> _loadData() async {
try {
final data = await apiClient.getData(
cancelToken: _requestCanceler.createCancelToken(),
);
// 处理数据
} catch (e) {
// 处理异常
}
}
@override
void dispose() {
_requestCanceler.cancelAll();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
常见陷阱与规避措施
-
超时设置不当
- 陷阱:所有请求使用相同的超时设置
- 解决方案:根据请求类型设置不同超时
// 为不同请求类型设置不同超时 class ApiClient { // 普通GET请求 Future<T> get<T>(String path) async { return _dio.get( path, options: Options( sendTimeout: Duration(seconds: 3), receiveTimeout: Duration(seconds: 5), ), ); } // 文件上传 Future<T> upload<T>(String path, FormData data) async { return _dio.post( path, data: data, options: Options( sendTimeout: Duration(minutes: 2), receiveTimeout: Duration(minutes: 1), ), ); } } -
未处理取消异常
- 陷阱:取消请求时未捕获Cancel异常
- 解决方案:专门处理取消异常,避免错误提示
try { // 网络请求 } on DioException catch (e) { if (e.type == DioExceptionType.cancel) { // 取消请求,不显示错误提示 debugPrint('请求已取消: ${e.message}'); } else { // 显示错误提示 showError(e.message); } } -
全局异常处理掩盖问题
- 陷阱:全局拦截器吞掉所有异常
- 解决方案:关键业务异常必须传播到UI层
// 错误拦截器中区分处理 @override Future<void> onError(DioException err, ErrorInterceptorHandler handler) async { // 记录和上报异常 _logError(err); _reportError(err); // 关键业务异常继续传播 if (_isCriticalError(err)) { handler.next(err); } else { // 非关键异常返回默认响应 handler.resolve(_createDefaultResponse(err.requestOptions)); } } // 判断是否为关键错误 bool _isCriticalError(DioException err) { // 支付、订单等关键接口异常需要传播 return err.requestOptions.path.contains('/payment/') || err.requestOptions.path.contains('/order/'); } -
重试机制使用不当
- 陷阱:对写操作进行重试导致重复提交
- 解决方案:仅对幂等请求进行重试
// 判断是否为幂等请求 bool _isIdempotentRequest(RequestOptions options) { // GET、HEAD、PUT、DELETE通常是幂等的 return ['GET', 'HEAD', 'PUT', 'DELETE'].contains(options.method.toUpperCase()); } // 重试逻辑中使用 if (_shouldRetry(e) && _isIdempotentRequest(e.requestOptions)) { // 执行重试 }
总结与展望
Dio异常处理是构建健壮Flutter应用的关键环节,需要从异常捕获、类型判断、错误处理到用户体验优化的全流程设计。本文系统介绍了Dio异常处理的原理与实践,提供了从基础到进阶的完整解决方案。
通过实现分层异常处理、全局拦截器、请求重试与熔断、缓存降级和离线支持等机制,可以构建企业级的网络异常处理系统。同时,遵循最佳实践并规避常见陷阱,能够显著提升应用的稳定性和用户体验。
随着网络技术的发展,未来Dio异常处理可能会向智能化方向发展,包括基于机器学习的异常预测、自适应超时调整和动态降级策略等。开发者需要持续关注Dio的更新,不断优化异常处理策略,构建更加健壮的网络请求系统。
掌握本文介绍的异常处理技术,你将能够:
- 构建稳定可靠的网络请求系统
- 显著提升应用在弱网环境下的表现
- 提供一致且友好的用户体验
- 快速定位和解决网络相关问题
希望本文能帮助你攻克Dio异常处理的难题,构建高质量的Flutter应用!
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust099- DDeepSeek-V4-ProDeepSeek-V4-Pro(总参数 1.6 万亿,激活 49B)面向复杂推理和高级编程任务,在代码竞赛、数学推理、Agent 工作流等场景表现优异,性能接近国际前沿闭源模型。Python00
MiMo-V2.5-ProMiMo-V2.5-Pro作为旗舰模型,擅⻓处理复杂Agent任务,单次任务可完成近千次⼯具调⽤与⼗余轮上 下⽂压缩。Python00
GLM-5.1GLM-5.1是智谱迄今最智能的旗舰模型,也是目前全球最强的开源模型。GLM-5.1大大提高了代码能力,在完成长程任务方面提升尤为显著。和此前分钟级交互的模型不同,它能够在一次任务中独立、持续工作超过8小时,期间自主规划、执行、自我进化,最终交付完整的工程级成果。Jinja00
Kimi-K2.6Kimi K2.6 是一款开源的原生多模态智能体模型,在长程编码、编码驱动设计、主动自主执行以及群体任务编排等实用能力方面实现了显著提升。Python00
MiniMax-M2.7MiniMax-M2.7 是我们首个深度参与自身进化过程的模型。M2.7 具备构建复杂智能体应用框架的能力,能够借助智能体团队、复杂技能以及动态工具搜索,完成高度精细的生产力任务。Python00