首页
/ 攻克Dio网络异常:从原理剖析到企业级解决方案

攻克Dio网络异常:从原理剖析到企业级解决方案

2026-04-19 08:46:10作者:管翌锬

网络异常处理是移动应用开发中的关键环节,直接影响用户体验和应用稳定性。作为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异常通过两种路径传播:

  1. 直接抛出:通过try/catch捕获的同步异常
  2. 回调传递:通过拦截器链传递的异步异常

拦截器中的异常处理流程如下:

// 简化的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;
}

最佳实践与陷阱规避

异常处理最佳实践

  1. 分层异常处理

    • 底层:Dio异常捕获与转换
    • 中间层:业务异常处理与转换
    • 上层:UI层错误展示与用户交互
  2. 异常信息标准化

    • 定义统一的错误码体系
    • 提供用户友好的错误消息
    • 保留技术细节用于调试
  3. 请求取消机制

    • 为每个请求创建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();
  }
}

常见陷阱与规避措施

  1. 超时设置不当

    • 陷阱:所有请求使用相同的超时设置
    • 解决方案:根据请求类型设置不同超时
    // 为不同请求类型设置不同超时
    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),
          ),
        );
      }
    }
    
  2. 未处理取消异常

    • 陷阱:取消请求时未捕获Cancel异常
    • 解决方案:专门处理取消异常,避免错误提示
    try {
      // 网络请求
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) {
        // 取消请求,不显示错误提示
        debugPrint('请求已取消: ${e.message}');
      } else {
        // 显示错误提示
        showError(e.message);
      }
    }
    
  3. 全局异常处理掩盖问题

    • 陷阱:全局拦截器吞掉所有异常
    • 解决方案:关键业务异常必须传播到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/');
    }
    
  4. 重试机制使用不当

    • 陷阱:对写操作进行重试导致重复提交
    • 解决方案:仅对幂等请求进行重试
    // 判断是否为幂等请求
    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应用!

登录后查看全文
热门项目推荐
相关项目推荐