3步打造Dio自定义拦截器:告别重复请求与冗余代码

【免费下载链接】dio A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc. 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/di/dio

你是否还在为每个API请求重复编写认证头?是否因重复请求相同数据而浪费带宽?是否在处理网络错误时陷入繁琐的try-catch嵌套?本文将通过3个实战步骤,带你从零构建Dio自定义拦截器,彻底解决这些开发痛点。读完本文,你将掌握拦截器的工作原理、核心方法实现及高级应用技巧,让网络请求代码从此简洁高效。

拦截器:Dio的"超级中间件"

Dio作为Flutter生态最强大的HTTP客户端,其拦截器(Interceptor)机制允许开发者在请求发送前、响应返回后、错误发生时插入自定义逻辑。这种AOP(面向切面编程)思想能帮我们优雅实现:

  • 全局认证Token自动附加
  • 网络请求缓存策略
  • 统一错误处理与重试
  • 请求/响应日志打印

Dio拦截器的工作流程如下:

mermaid

核心拦截器基类定义在dio/lib/src/interceptor.dart,包含三个生命周期方法:

  • onRequest: 请求发送前触发,可修改请求参数
  • onResponse: 响应返回后触发,可处理响应数据
  • onError: 请求出错时触发,可统一错误处理

第一步:创建基础拦截器

让我们从最简单的日志拦截器开始,理解拦截器的基本结构。Dio官方已提供LogInterceptor,但通过手动实现能更好掌握原理。

基础模板实现

import 'package:dio/dio.dart';

class SimpleLogInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('📤 请求 ${options.method} ${options.uri}');
    print('   参数: ${options.data ?? "空"}');
    handler.next(options); // 必须调用next()传递请求
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('📥 响应 ${response.statusCode} ${response.requestOptions.uri}');
    print('   数据: ${response.data}');
    handler.next(response); // 传递响应
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('❌ 错误 ${err.type} ${err.requestOptions.uri}');
    print('   原因: ${err.message}');
    handler.next(err); // 传递错误
  }
}

关键方法解析

  1. handler.next()
    必须调用此方法将处理后的对象传递给下一个拦截器,否则请求会被中断。这类似于Express.js中间件的next()函数。

  2. 拦截器注册
    创建后需添加到Dio实例的拦截器队列:

final dio = Dio();
dio.interceptors.add(SimpleLogInterceptor());
  1. 执行顺序
    拦截器按添加顺序执行,日志拦截器建议放在最后,确保能记录其他拦截器修改后的最终请求。

第二步:构建实用缓存拦截器

缓存是拦截器的经典应用场景。我们将实现一个支持强制刷新、键值存储的缓存拦截器,完整代码参考example_dart/lib/custom_cache_interceptor.dart

核心实现

class CacheInterceptor extends Interceptor {
  // 使用Map作为内存缓存容器
  final _cache = <Uri, Response>{};

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 1. 检查是否需要强制刷新
    if (options.extra['refresh'] == true) {
      print('${options.uri}: 强制刷新,忽略缓存');
      return handler.next(options);
    }

    // 2. 检查缓存是否存在
    final cachedResponse = _cache[options.uri];
    if (cachedResponse != null) {
      print('${options.uri}: 命中缓存');
      // 直接返回缓存响应,不再发起网络请求
      return handler.resolve(cachedResponse);
    }

    // 3. 无缓存,继续请求
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 将成功响应存入缓存
    _cache[response.requestOptions.uri] = response;
    handler.next(response);
  }
}

高级特性扩展

  1. 缓存过期策略
    添加时间戳实现缓存过期:
// 缓存项模型
class CacheItem {
  final Response response;
  final DateTime timestamp;
  
  CacheItem(this.response) : timestamp = DateTime.now();
  
  // 判断是否过期(5分钟有效期)
  bool get isExpired => DateTime.now().difference(timestamp).inMinutes > 5;
}
  1. 缓存清除API
    提供手动清除缓存的方法:
void clearCache({Uri? uri}) {
  if (uri != null) {
    _cache.remove(uri);
  } else {
    _cache.clear();
  }
}
  1. 使用示例
dio.interceptors.add(CacheInterceptor());

// 正常请求(会缓存)
await dio.get('/api/data');

// 强制刷新(不使用缓存)
await dio.get('/api/data', options: Options(extra: {'refresh': true}));

第三步:企业级拦截器最佳实践

在实际项目中,我们通常需要组合多个拦截器形成"拦截器链"。以下是生产环境推荐的拦截器配置方案:

拦截器组合示例

dio.interceptors
  ..add(AuthInterceptor())       // 认证Token处理
  ..add(CacheInterceptor())       // 缓存策略
  ..add(RetryInterceptor())       // 失败重试
  ..add(LogInterceptor(           // 日志打印
    requestBody: true,
    responseBody: true,
    logPrint: debugPrint         // Flutter中使用debugPrint避免日志截断
  ));

认证拦截器实现要点

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 1. 添加Token
    final token = _getTokenFromStorage();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    
    // 2. Token过期处理
    if (_isTokenExpired() && !options.path.contains('/refresh')) {
      return _refreshToken().then((newToken) {
        options.headers['Authorization'] = 'Bearer $newToken';
        handler.next(options);
      }).catchError((error) {
        handler.reject(DioException(
          requestOptions: options,
          error: 'Token expired',
          type: DioExceptionType.cancel,
        ));
      });
    }
    
    handler.next(options);
  }
}

调试与测试技巧

  1. 使用日志拦截器
    官方LogInterceptor提供细粒度日志控制,建议在开发环境开启完整日志:
LogInterceptor(
  requestHeader: true,
  requestBody: true,
  responseHeader: true,
  responseBody: true,
)
  1. 拦截器单元测试
    Dio提供测试工具类,可参考dio/test/interceptor_test.dart编写单元测试:
void main() {
  test('CacheInterceptor should return cached response', () async {
    final dio = Dio();
    final cacheInterceptor = CacheInterceptor();
    dio.interceptors.add(cacheInterceptor);
    
    // 第一次请求(缓存未命中)
    await dio.get('https://httpbin.org/get');
    // 第二次请求(应命中缓存)
    final response = await dio.get('https://httpbin.org/get');
    
    expect(response.extra['fromCache'], true);
  });
}

拦截器高级应用场景

1. 网络状态感知

结合connectivity_plus插件实现网络状态判断:

class NetworkInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final connectivityResult = await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      return handler.reject(DioException(
        requestOptions: options,
        type: DioExceptionType.connectionError,
        error: 'No internet connection',
      ));
    }
    handler.next(options);
  }
}

2. 请求防抖节流

防止短时间内重复请求相同资源:

class ThrottleInterceptor extends Interceptor {
  final _lastRequestTimes = <String, DateTime>{};
  
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final key = options.uri.toString();
    final now = DateTime.now();
    final lastTime = _lastRequestTimes[key];
    
    if (lastTime != null && now.difference(lastTime).inMilliseconds < 500) {
      // 500ms内重复请求,直接拦截
      return handler.reject(DioException(
        requestOptions: options,
        type: DioExceptionType.cancel,
        error: 'Request throttled',
      ));
    }
    
    _lastRequestTimes[key] = now;
    handler.next(options);
  }
}

避坑指南与性能优化

常见错误

  1. 忘记调用handler方法
    未调用handler.next()/resolve()/reject()会导致请求永久挂起,务必确保每个代码路径都有处理。

  2. 拦截器顺序错误
    例如缓存拦截器应在认证拦截器之后,否则缓存的请求可能不含最新Token。

  3. 耗时操作阻塞
    拦截器回调中避免同步耗时操作,可使用compute函数或Isolate处理复杂计算。

性能优化建议

  1. 内存管理
    大型应用建议使用LRU缓存策略限制缓存大小,避免内存泄漏:
// 使用lru_cache包
import 'package:lru_cache/lru_cache.dart';
final _cache = LruCache<Uri, Response>(maxSize: 100); // 最多缓存100条
  1. 条件执行
    通过options.extra传递自定义参数,实现拦截器功能的动态开关:
if (options.extra['skipCache'] == true) return handler.next(options);

总结与进阶路线

通过本文学习,你已掌握Dio拦截器的核心原理与实现方法。拦截器作为Dio最强大的特性之一,其应用远不止于本文介绍的场景。建议进一步学习:

  • 拦截器优先级控制:通过QueuedInterceptor实现异步串行执行
  • 自定义适配器:结合dio/lib/src/adapter.dart实现底层请求逻辑替换
  • 插件化开发:参考plugins/目录下的官方插件,开发可复用的拦截器插件

最后,推荐收藏Dio官方文档与示例代码库,持续关注最新特性:

点赞+收藏本文,关注作者获取更多Dio进阶技巧!下一期我们将深入讲解"基于拦截器的Flutter离线优先架构"。

Dio拦截器架构图

【免费下载链接】dio A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc. 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/di/dio

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐