99%的开发者都踩过的坑:Dio异常处理完全指南(错误捕获+优雅降级)

99%的开发者都踩过的坑:Dio异常处理完全指南(错误捕获+优雅降级)

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

你是否遇到过APP突然闪退?用户投诉"加载半天没反应"?接口返回500时页面一片空白?这些问题往往源于异常处理的缺失。Dio作为Flutter生态中最流行的网络请求库,其异常处理机制直接决定了APP的稳定性和用户体验。本文将带你系统掌握Dio异常处理的全流程,从错误捕获到优雅降级,让你的APP从此告别崩溃。

读完本文你将获得:

  • 识别8种Dio异常类型的能力
  • 3种错误捕获的实战技巧
  • 5步实现请求优雅降级
  • 完整的异常处理代码模板

Dio异常体系解析

Dio定义了完整的异常类型体系,所有网络错误都会被封装为DioException对象。通过分析dio/lib/src/dio_exception.dart源码,我们可以将异常分为以下8种类型:

异常类型触发场景典型案例
connectionTimeout建立连接超时服务器无响应
sendTimeout发送数据超时上传大文件网络慢
receiveTimeout接收数据超时下载文件超时
badCertificate证书验证失败HTTPS证书过期
badResponse非预期状态码404/500响应
cancel请求被取消用户主动取消
connectionError网络连接错误无网络/WiFi断开
unknown未知错误序列化失败

异常类核心结构

DioException类包含5个关键属性,帮助我们全面定位问题:

class DioException implements Exception {
  final RequestOptions requestOptions; // 请求配置
  final Response? response; // 响应数据(可能为null)
  final DioExceptionType type; // 异常类型
  final Object? error; // 原始错误对象
  final String? message; // 错误描述
}

异常捕获实战

1. try/catch基础捕获

最直接的异常捕获方式是使用try/catch包裹请求代码:

try {
  final response = await dio.get('/api/data');
  // 处理正常响应
} on DioException catch (e) {
  // 捕获Dio异常
  handleDioError(e);
} catch (e) {
  // 捕获其他类型异常
  print('其他错误: $e');
}

2. 拦截器全局捕获

通过拦截器可以实现全局异常统一处理,避免重复代码:

dio.interceptors.add(InterceptorsWrapper(
  onError: (DioException e, handler) {
    // 全局异常处理逻辑
    logError(e);
    showErrorToast(e);
    
    // 根据异常类型决定是否继续传播
    if (e.type == DioExceptionType.cancel) {
      return handler.reject(e); // 取消请求不提示
    }
    return handler.next(e);
  },
));

3. 取消请求处理

Dio提供了CancelToken机制,用于主动取消请求。在dio/lib/src/cancel_token.dart中定义了取消相关逻辑:

// 创建取消令牌
final cancelToken = CancelToken();

// 发起请求时关联令牌
dio.get('/api/data', cancelToken: cancelToken)
  .then((response) {})
  .catchError((e) {
    if (CancelToken.isCancel(e)) {
      print('请求已取消: ${e.message}');
    }
  });

// 需要取消时调用
cancelToken.cancel('用户主动取消');

异常类型判断与处理

针对不同异常类型,我们需要采取差异化的处理策略。以下是完整的异常处理函数:

void handleDioError(DioException e) {
  switch (e.type) {
    case DioExceptionType.connectionTimeout:
      showError('网络连接超时,请检查网络');
      break;
    case DioExceptionType.sendTimeout:
      showError('发送数据超时,请稍后重试');
      break;
    case DioExceptionType.receiveTimeout:
      showError('接收数据超时,请检查网络');
      break;
    case DioExceptionType.badCertificate:
      showError('证书验证失败,无法建立安全连接');
      break;
    case DioExceptionType.badResponse:
      handleBadResponse(e.response!);
      break;
    case DioExceptionType.cancel:
      print('请求已取消: ${e.message}');
      break;
    case DioExceptionType.connectionError:
      showError('网络连接错误,请检查网络设置');
      break;
    case DioExceptionType.unknown:
      showError('未知错误: ${e.message ?? e.error}');
      break;
  }
}

// 处理非预期状态码
void handleBadResponse(Response response) {
  switch (response.statusCode) {
    case 400:
      showError('请求参数错误: ${response.data['message']}');
      break;
    case 401:
      // 处理未授权,通常是token过期
      showError('登录已过期,请重新登录');
      logout();
      break;
    case 403:
      showError('没有权限访问该资源');
      break;
    case 404:
      showError('请求的资源不存在');
      break;
    case 500:
      showError('服务器内部错误,请稍后重试');
      break;
    default:
      showError('服务器返回异常: ${response.statusCode}');
  }
}

优雅降级策略

当网络出现问题时,良好的降级策略能极大提升用户体验。以下是5步实现优雅降级的方案:

1. 请求重试机制

对临时性错误实现自动重试:

Future<T> requestWithRetry<T>({
  required Future<T> Function() request,
  int maxRetries = 2,
}) async {
  int retries = 0;
  while (true) {
    try {
      return await request();
    } on DioException catch (e) {
      retries++;
      if (retries >= maxRetries || !_shouldRetry(e)) {
        rethrow;
      }
      // 指数退避策略
      final delay = Duration(milliseconds: 300 * (1 << (retries - 1)));
      await Future.delayed(delay);
    }
  }
}

// 判断是否应该重试
bool _shouldRetry(DioException e) {
  return e.type == DioExceptionType.connectionError ||
         e.type == DioExceptionType.connectionTimeout ||
         (e.type == DioExceptionType.badResponse && 
          e.response?.statusCode != null && 
          e.response!.statusCode! >= 500);
}

2. 缓存优先策略

使用缓存数据应对网络异常:

class CacheInterceptor extends Interceptor {
  final CacheManager cacheManager;

  @override
  Future<void> onRequest(
    RequestOptions options, 
    RequestInterceptorHandler handler
  ) async {
    // 检查是否有缓存数据
    final cacheData = await cacheManager.getCache(options.uri.toString());
    if (cacheData != null && options.extra['forceRefresh'] != true) {
      // 返回缓存数据
      return handler.resolve(Response(
        requestOptions: options,
        data: cacheData,
        statusCode: 200,
      ));
    }
    return handler.next(options);
  }
  
  // 其他拦截器方法...
}

3. 友好错误提示

根据异常类型提供人性化提示:

void showErrorToast(DioException e) {
  String message;
  switch (e.type) {
    case DioExceptionType.connectionError:
      message = '网络连接失败,请检查网络设置';
      break;
    case DioExceptionType.connectionTimeout:
      message = '连接超时,请稍后重试';
      break;
    case DioExceptionType.receiveTimeout:
      message = '数据加载超时,请检查网络';
      break;
    case DioExceptionType.badResponse:
      message = _getResponseErrorMsg(e.response!.statusCode!);
      break;
    default:
      message = '请求失败,请稍后重试';
  }
  
  // 显示提示
  Fluttertoast.showToast(msg: message);
}

4. 离线数据支持

使用本地数据库存储关键数据,在无网络时提供基础功能:

class OfflineDataRepository {
  final Dio dio;
  final LocalDatabase db;

  Future<List<Item>> getItems() async {
    try {
      // 优先从网络获取
      final response = await dio.get('/items');
      final items = (response.data as List).map((e) => Item.fromJson(e)).toList();
      // 保存到本地数据库
      await db.saveItems(items);
      return items;
    } on DioException catch (e) {
      if (e.type == DioExceptionType.connectionError) {
        // 网络错误时返回本地数据
        return db.getItems();
      }
      rethrow;
    }
  }
}

5. 功能降级开关

通过功能开关控制非核心功能的可用性:

class FeatureManager {
  // 根据网络状态决定功能可用性
  bool isFeatureAvailable(String feature) {
    final networkState = _networkMonitor.currentState;
    if (networkState == NetworkState.offline) {
      return _offlineAvailableFeatures.contains(feature);
    }
    return true;
  }
}

// UI层使用
if (featureManager.isFeatureAvailable('videoUpload')) {
  // 显示视频上传按钮
} else {
  // 隐藏或禁用按钮
}

异常监控与分析

完善的异常处理还需要监控系统的支持,以下是一个简单的异常上报实现:

class ErrorReporter {
  static void report(DioException e) async {
    try {
      await dio.post('/api/error-report', data: {
        'type': e.type.toString(),
        'message': e.message,
        'requestUrl': e.requestOptions.uri.toString(),
        'requestMethod': e.requestOptions.method,
        'statusCode': e.response?.statusCode,
        'timestamp': DateTime.now().toIso8601String(),
        'deviceInfo': await _getDeviceInfo(),
      });
    } catch (reportError) {
      // 确保上报本身的错误不会影响主流程
      print('上报错误失败: $reportError');
    }
  }
}

完整代码模板

最后,为你提供一个完整的Dio异常处理模板,可直接集成到项目中:

import 'package:dio/dio.dart';
import 'package:dio/io.dart';

class NetworkClient {
  final Dio _dio;
  
  NetworkClient() : _dio = Dio() {
    _setupDio();
  }
  
  void _setupDio() {
    // 基础配置
    _dio.options.baseUrl = 'https://api.example.com';
    _dio.options.connectTimeout = Duration(seconds: 5);
    _dio.options.receiveTimeout = Duration(seconds: 3);
    
    // 添加拦截器
    _dio.interceptors.add(LogInterceptor(responseBody: true));
    _dio.interceptors.add(ErrorInterceptor());
    
    // 配置适配器
    _dio.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () {
        final client = HttpClient();
        // 配置证书校验等
        return client;
      },
    );
  }
  
  // 带重试的GET请求
  Future<T> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    int maxRetries = 2,
  }) async {
    return requestWithRetry(
      request: () => _dio.get(
        path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      ),
      maxRetries: maxRetries,
    );
  }
  
  // 其他请求方法...
}

// 全局错误拦截器
class ErrorInterceptor extends Interceptor {
  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    // 错误处理逻辑
    _logError(err);
    _showErrorToast(err);
    _reportError(err);
    
    handler.next(err);
  }
  
  void _logError(DioException err) {
    // 日志记录
  }
  
  void _showErrorToast(DioException err) {
    // 显示错误提示
  }
  
  void _reportError(DioException err) {
    // 错误上报
  }
}

// 异常处理工具类
class ExceptionHandler {
  static void handle(DioException e) {
    // 异常处理逻辑
  }
}

总结

Dio异常处理是每个Flutter开发者必备技能,良好的异常处理能显著提升APP质量。本文从异常类型、捕获方式、处理策略到优雅降级,全面覆盖了Dio异常处理的各个方面。记住,稳定的APP不是没有错误,而是能妥善处理每一个可能的错误。

掌握这些技巧后,你可以:

  • 精准定位各类网络错误
  • 实现全局统一的异常处理
  • 为用户提供友好的错误提示
  • 在网络异常时保持功能可用

希望本文能帮助你构建更健壮的Flutter应用,让你的APP在各种网络环境下都能提供出色的用户体验!

如果觉得本文对你有帮助,别忘了点赞、收藏、关注三连,下期将为你带来"Dio性能优化实战"!

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值