3步打造Dio自定义拦截器:告别重复请求与冗余代码
你是否还在为每个API请求重复编写认证头?是否因重复请求相同数据而浪费带宽?是否在处理网络错误时陷入繁琐的try-catch嵌套?本文将通过3个实战步骤,带你从零构建Dio自定义拦截器,彻底解决这些开发痛点。读完本文,你将掌握拦截器的工作原理、核心方法实现及高级应用技巧,让网络请求代码从此简洁高效。## 拦截器:Dio的"超级中间件"Dio作为Flutter生态最强大的HTTP客户端,其拦...
3步打造Dio自定义拦截器:告别重复请求与冗余代码
你是否还在为每个API请求重复编写认证头?是否因重复请求相同数据而浪费带宽?是否在处理网络错误时陷入繁琐的try-catch嵌套?本文将通过3个实战步骤,带你从零构建Dio自定义拦截器,彻底解决这些开发痛点。读完本文,你将掌握拦截器的工作原理、核心方法实现及高级应用技巧,让网络请求代码从此简洁高效。
拦截器:Dio的"超级中间件"
Dio作为Flutter生态最强大的HTTP客户端,其拦截器(Interceptor)机制允许开发者在请求发送前、响应返回后、错误发生时插入自定义逻辑。这种AOP(面向切面编程)思想能帮我们优雅实现:
- 全局认证Token自动附加
- 网络请求缓存策略
- 统一错误处理与重试
- 请求/响应日志打印
Dio拦截器的工作流程如下:
核心拦截器基类定义在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); // 传递错误
}
}
关键方法解析
-
handler.next()
必须调用此方法将处理后的对象传递给下一个拦截器,否则请求会被中断。这类似于Express.js中间件的next()函数。 -
拦截器注册
创建后需添加到Dio实例的拦截器队列:
final dio = Dio();
dio.interceptors.add(SimpleLogInterceptor());
- 执行顺序
拦截器按添加顺序执行,日志拦截器建议放在最后,确保能记录其他拦截器修改后的最终请求。
第二步:构建实用缓存拦截器
缓存是拦截器的经典应用场景。我们将实现一个支持强制刷新、键值存储的缓存拦截器,完整代码参考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);
}
}
高级特性扩展
- 缓存过期策略
添加时间戳实现缓存过期:
// 缓存项模型
class CacheItem {
final Response response;
final DateTime timestamp;
CacheItem(this.response) : timestamp = DateTime.now();
// 判断是否过期(5分钟有效期)
bool get isExpired => DateTime.now().difference(timestamp).inMinutes > 5;
}
- 缓存清除API
提供手动清除缓存的方法:
void clearCache({Uri? uri}) {
if (uri != null) {
_cache.remove(uri);
} else {
_cache.clear();
}
}
- 使用示例
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);
}
}
调试与测试技巧
- 使用日志拦截器
官方LogInterceptor提供细粒度日志控制,建议在开发环境开启完整日志:
LogInterceptor(
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
)
- 拦截器单元测试
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);
}
}
避坑指南与性能优化
常见错误
-
忘记调用handler方法
未调用handler.next()/resolve()/reject()会导致请求永久挂起,务必确保每个代码路径都有处理。 -
拦截器顺序错误
例如缓存拦截器应在认证拦截器之后,否则缓存的请求可能不含最新Token。 -
耗时操作阻塞
拦截器回调中避免同步耗时操作,可使用compute函数或Isolate处理复杂计算。
性能优化建议
- 内存管理
大型应用建议使用LRU缓存策略限制缓存大小,避免内存泄漏:
// 使用lru_cache包
import 'package:lru_cache/lru_cache.dart';
final _cache = LruCache<Uri, Response>(maxSize: 100); // 最多缓存100条
- 条件执行
通过options.extra传递自定义参数,实现拦截器功能的动态开关:
if (options.extra['skipCache'] == true) return handler.next(options);
总结与进阶路线
通过本文学习,你已掌握Dio拦截器的核心原理与实现方法。拦截器作为Dio最强大的特性之一,其应用远不止于本文介绍的场景。建议进一步学习:
- 拦截器优先级控制:通过
QueuedInterceptor实现异步串行执行 - 自定义适配器:结合dio/lib/src/adapter.dart实现底层请求逻辑替换
- 插件化开发:参考plugins/目录下的官方插件,开发可复用的拦截器插件
最后,推荐收藏Dio官方文档与示例代码库,持续关注最新特性:
点赞+收藏本文,关注作者获取更多Dio进阶技巧!下一期我们将深入讲解"基于拦截器的Flutter离线优先架构"。
更多推荐




所有评论(0)