前言
在移动应用开发中,网络请求 如同血管中的血液
,承载着数据交互的生命力
。然而,你是否遇到过这样的场景:每个请求都要手动添加Token
、全局处理错误码
、统一添加埋点日志
......这些重复性工作不仅效率低下,更让代码臃肿难维护。
Dio
拦截器,正是为解决这些问题而生的利器 。它像一位隐形的"网络请求调度员"
,在请求发出前 、响应返回后 、甚至错误发生时,以流水线的方式对数据进行加工和拦截。
本文将带你深入Dio
拦截器的底层逻辑 ,通过系统化的思维拆解其设计哲学,并通过实战案例展示如何用它构建高扩展性的网络层。你是否准备好,让代码从此优雅起来?
操千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意。
一、基本概念
1.1、本质定义
拦截器 是
Dio
网络库提供的一种中间件机制 ,允许在网络请求的生命周期中 (请求发出前
、响应返回后
、错误发生时
)插入自定义逻辑。它通过链式处理模型 ,将多个独立的处理单元 (
拦截器
)按顺序串联,形成一个可扩展的流水线流程
。
深入理解 :
想象一家外卖配送中心:
- 1、接单员 (
onRequest
)检查订单完整性。 - 2、打包员 添加餐具(
添加请求头
)。 - 3、配送员 (
onResponse
)检查餐品是否完好。 - 4、客服 (
onError
)处理配送异常 。
拦截器 就是这些分工明确的"岗位"
,每个环节专注单一职责 ,通过链式传递完成整个流程
。
1.2、处理流程
Dio
拦截器的工作流程分为三个阶段,每个阶段对应一个核心方法:
阶段 | 方法 | 触发时机 | 典型操作 |
---|---|---|---|
请求前处理 | onRequest |
请求即将发送到服务器之前 | 修改请求头、添加公共参数、加密请求体 |
响应后处理 | onResponse |
服务器返回响应且HTTP 状态码为2xx |
解析业务数据、转换数据结构、缓存响应结果 |
错误处理 | onError |
请求失败或HTTP 状态码非2xx |
统一错误提示、重试机制、刷新Token |
流程示例:
dart
// 请求 -> 拦截器A的onRequest -> 拦截器B的onRequest -> 发送请求
// 响应 -> 拦截器B的onResponse -> 拦截器A的onResponse -> 返回结果
1.3、技术特征
①、链式传递 :
拦截器按照添加顺序形成处理链,每个拦截器处理完成后,通过调用 handler.next()
将控制权传递给下一个拦截器,直到所有拦截器执行完毕。
dart
dio.interceptors.add(InterceptorA()); // 先执行
dio.interceptors.add(InterceptorB()); // 后执行
②、中断与短路 :
在任意阶段,拦截器可通过 handler.resolve()
或 handler.reject()
直接返回结果
或抛出错误
,终止后续拦截器的执行。
dart
onRequest: (options, handler) {
if (无网络连接) {
handler.reject(DioException(message: '网络不可用')); // 终止请求
} else {
handler.next(options); // 继续传递
}
}
③、上下文隔离 :
每个拦截器仅能访问当前阶段的请求/响应对象 (RequestOptions
、Response
),无法直接修改其他拦截器的内部状态,保证职责单一性。
1.4、职责边界
拦截器的核心职责可归纳为以下四类:
职责类型 | 具体场景 | 代码示例 |
---|---|---|
数据加工 | 添加全局请求头、参数加密、数据序列化 | options.headers['Authorization'] = 'Bearer $token' |
流程控制 | 重试失败请求、等待Token 刷新后继续 |
handler.retry(request) |
监控与统计 | 记录请求耗时、上报接口成功率 | log('API耗时:${DateTime.now().difference(startTime)}') |
异常处理 | 统一错误码映射、弹窗提示、降级处理 | if (error.response?.statusCode == 504) showTimeoutDialog() |
二、核心价值
2.1、统一处理:解决代码重复性问题
问题场景 :
在未使用拦截器时,开发者需要在每个网络请求中手动处理以下逻辑:
- 添加认证
Token
。 - 解析业务状态码 (如接口返回的
code
字段)。 - 记录请求日志。
- 错误重试 或
Token
刷新。
代码反例:
dart
// 每个请求都需重复相同逻辑
Future<User> fetchData() async {
try {
final response = await dio.get(
'/api/user',
options: Options(headers: {'Authorization': 'Bearer $token'}), // 手动添加Token
);
// 手动解析业务状态码
if (response.data['code'] != 200) {
throw AppException(response.data['message']);
}
return User.fromJson(response.data);
} on DioException catch (e) {
// 手动处理错误
if (e.response?.statusCode == 401) {
await refreshToken();
return fetchData(); // 重试请求
}
rethrow;
}
}
拦截器价值:
通过拦截器将上述逻辑封装为独立模块,所有请求自动继承这些行为,减少
90%
的重复代码。
2.2、职责分离:实现网络层
与业务逻辑
解耦(高内聚低耦合
)
问题场景 :
当网络层逻辑 (如加密算法
、缓存策略
)直接嵌入业务代码时:
- 业务
代码臃肿
,可读性下降。 - 修改网络逻辑需全局搜索替换,容易
引入错误
。 - 不同模块可能实现
不一致
的网络处理逻辑。
通过拦截器,可以将网络层的关注点分解为独立模块:
- 横向分层 :
- 基础层 (
日志
、加密
)、业务层 (Token刷新
、错误提示
)、监控层 (性能统计
)。
- 基础层 (
- 纵向隔离 :
- 不同模块的拦截器互不干扰,例如日志拦截器无需感知加密逻辑 。
无拦截器方案:
dart
// 无拦截器:业务代码中混杂网络逻辑
Future<User> fetchUser() async {
try {
// 手动添加Token
final response = await dio.get('/user', options: Options(
headers: {'Authorization': 'Bearer $token'},
));
// 手动解析业务状态码
if (response.data['code'] != 200) {
throw CustomError(response.data['message']);
}
return User.fromJson(response.data);
} on DioException catch (e) {
// 手动处理错误
showErrorDialog(e.message);
}
}
拦截器方案:
dart
// 业务代码
Future<User> fetchData() async {
return dio.get('/api/user').then((res) => User.fromJson(res.data));
}
// 网络层独立模块
dio.interceptors.addAll([
AuthInterceptor(), // 认证
LoggingInterceptor(), // 日志
RetryInterceptor(), // 重试
BizCodeInterceptor(), // 业务状态码解析
]);
核心优势:
- 业务代码纯粹 :仅
关注数据转换
,不涉及网络细节
。 - 网络层可独立演进 :修改
加密算法
或缓存策略
时,无需改动业务代码。
2.3、动态扩展:灵活调整请求流程
核心能力 :
拦截器支持在运行时动态添加或移除 ,无需修改业务代码即可调整网络层行为,实现"热插拔"
式的功能扩展。
典型场景 :
①、环境切换:
dart
// 开发环境:添加日志拦截器
if (isDev) dio.interceptors.add(LogInterceptor());
// 生产环境:移除日志,添加加密拦截器
if (isProd) {
dio.interceptors.removeWhere((i) => i is LogInterceptor);
dio.interceptors.add(EncryptInterceptor());
}
②、功能开关:
dart
// 根据用户设置动态启用/禁用埋点
analyticsEnabled ? dio.interceptors.add(AnalyticsInterceptor())
: dio.interceptors.remove(AnalyticsInterceptor());
③、按需加载:
dart
// 特定页面需要缓存拦截器
void openUserProfile() {
dio.interceptors.add(CacheInterceptor());
fetchUserData();
}
技术优势:
- 零侵入性:业务代码不感知拦截器的存在。
- 即时生效:修改拦截器后,后续请求自动应用新逻辑。
- 组合自由:通过不同拦截器的排列组合,快速实现多样化需求。
2.4、性能优化:批量处理耗时操作
核心机制 :
拦截器通过集中处理重复性计算任务 (如加密
、压缩
),减少单次请求的冗余开销,同时利用异步队列避免阻塞主线程。
典型场景 :
①、批量加密请求体:
dart
class EncryptInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, handler) async {
// 统一加密所有请求体(减少重复初始化加密工具的开销)
if (options.data is Map) {
options.data = _batchEncrypt(options.data); // 批量处理数据
}
handler.next(options);
}
}
②、异步并行处理:
dart
// 使用isolate或线程池并行处理多个响应解密
onResponse: (response, handler) async {
final List<dynamic> dataList = response.data as List;
// 并行解密列表中的每条数据
final decryptedData = await Future.wait(
dataList.map((item) => compute(decrypt, item))
);
response.data = decryptedData;
handler.next(response);
}
③、缓存计算结果:
dart
// 对相同请求参数缓存加密结果
final _encryptCache = HashMap<String, String>();
onRequest: (options, handler) {
final key = options.uri.toString();
if (_encryptCache.containsKey(key)) {
options.data = _encryptCache[key]; // 直接使用缓存
} else {
options.data = encrypt(options.data);
_encryptCache[key] = options.data;
}
handler.next(options);
}
优化收益:
- 减少
CPU
占用:避免每个请求独立初始化加密算法。 - 降低网络延迟:压缩后的数据体积更小,传输更快。
- 提升响应速度:异步处理避免主线程卡顿。
三、核心类
及方法
详解
3.1、核心类详解
①、Interceptor
:基类
所有自定义拦截器需继承自 Interceptor
,其核心生命周期方法:
dart
abstract class Interceptor {
void onRequest(RequestOptions options, RequestInterceptorHandler handler);
void onResponse(Response response, ResponseInterceptorHandler handler);
void onError(DioException err, ErrorInterceptorHandler handler);
}
②、RequestInterceptorHandler
、ResponseInterceptorHandler
、ErrorInterceptorHandler
:控制类
用于控制拦截器链的传递流程,核心方法:
dart
class RequestInterceptorHandler extends _BaseHandler {
void next(RequestOptions requestOptions) {
//...
}
void resolve(Response response, [bool callFollowingResponseInterceptor = false,]) {
//...
}
void reject(DioException error, [
bool callFollowingErrorInterceptor = false,]) {
//...
}
}
class ResponseInterceptorHandler extends _BaseHandler {
void next(Response response) {
//...
}
void resolve(Response response) {
//...
}
void reject(DioException error, [
bool callFollowingErrorInterceptor = false,]) {
//...
}
}
class ErrorInterceptorHandler extends _BaseHandler {
void next(DioException error) {
//...
}
void resolve(Response response) {
//...
}
void reject(DioException error) {
//...
}
}
③、QueuedInterceptor
:队列拦截器
顺序执行拦截器,适用于需要严格顺序的场景(如Token刷新
)。
3.2、方法参数详解
①、onRequest
方法
dart
void onRequest(
RequestOptions options, // 当前请求配置(可修改)
RequestInterceptorHandler handler
)
关键属性(RequestOptions
):
dart
options.method; // 请求方法(GET/POST等)
options.uri; // 请求地址
options.headers; // 请求头(可直接修改)
options.queryParameters; // URL查询参数
options.data; // 请求体数据
options.extra; // 自定义扩展参数(跨拦截器传递数据)
修改请求头:
dart
void onRequest(options, handler) {
options.headers.addAll({
'X-App-Version': '1.0.0',
'X-Device-ID': 'ABC123',
});
handler.next(options); // 传递修改后的配置
}
②、onResponse
方法
dart
void onResponse(
Response response, // 响应对象(可修改)
ResponseInterceptorHandler handler
)
关键属性(Response
):
dart
response.data; // 响应体数据(可修改为业务模型)
response.statusCode; // HTTP状态码
response.requestOptions; // 关联的请求配置
数据转换:
dart
void onResponse(response, handler) {
// 将原始JSON转换为业务模型
response.data = User.fromJson(response.data['user']);
handler.next(response);
}
③、onError
方法
dart
void onError(
DioException err, // 错误对象(可修改)
ErrorInterceptorHandler handler
)
关键属性(DioException
):
dart
err.type; // 错误类型(如DioExceptionType.connectionTimeout)
err.requestOptions; // 关联的请求配置
err.response; // 服务器返回的错误响应(若有)
错误重试:
dart
void onError(err, handler) async {
if (err.response?.statusCode == 401) {
// 刷新Token后重试原请求
final newToken = await refreshToken();
err.requestOptions.headers['Authorization'] = 'Bearer $newToken';
final newResponse = await dio.fetch(err.requestOptions);
handler.resolve(newResponse); // 终止错误链,返回新响应
} else {
handler.next(err); // 继续传递错误
}
}
3.3、高级控制技巧
①、中断链式传递
在任意阶段调用 handler.resolve()
或 handler.reject()
可提前终止拦截器链:
dart
void onRequest(options, handler) {
if (无网络连接) {
// 直接返回自定义错误
handler.reject(DioException(
requestOptions: options,
error: '网络不可用',
));
} else {
handler.next(options);
}
}
②、跨拦截器数据传递
通过 options.extra
字段在不同拦截器间共享数据:
dart
// 拦截器A
void onRequest(options, handler) {
options.extra['startTime'] = DateTime.now();
handler.next(options);
}
// 拦截器B
void onResponse(response, handler) {
final duration = DateTime.now().difference(
response.requestOptions.extra['startTime']
);
print('请求耗时:$duration');
handler.next(response);
}
③、并发控制(QueuedInterceptorsWrapper
)
使用队列拦截器确保异步操作顺序执行:
dart
dio.interceptors.add(QueuedInterceptorsWrapper(
onRequest: (options, handler) async {
// 确保同一时间只有一个请求进入
await _semaphore.acquire();
handler.next(options);
_semaphore.release();
},
));
3.4、方法调用全流程
①、请求阶段:
arduino
请求发起 → 拦截器1.onRequest → 拦截器2.onRequest → ... → 发送网络请求
②、响应阶段:
收到响应 → 最后一个拦截器.onResponse → ... → 拦截器1.onResponse → 返回结果
③、错误阶段:
发生错误 → 最后一个拦截器.onError → ... → 拦截器1.onError → 抛出异常
关键原则:
-
每个拦截器必须调用
handler.next()
、resolve()
或reject()
,否则链式调用会中断。 -
修改
RequestOptions
或Response
时需注意对象不可变性,推荐使用copyWith
方法。dart// 安全修改请求配置 final newOptions = options.copyWith( headers: {...options.headers, 'X-Foo': 'Bar'}, ); handler.next(newOptions);
四、实现原理
4.1、拦截器类型与生命周期
拦截器分为三类:
- 1、请求拦截器 :在发送请求前修改请求参数(如添加
Headers
、加密数据
)。 - 2、响应拦截器 :在收到响应后处理数据(如解析
JSON
、缓存结果
)。 - 3、错误拦截器 :统一处理请求过程中的异常(如
Token
过期、网络错误
)。
生命周期顺序为:
请求拦截器 → 发送请求 → 响应拦截器 → 错误拦截器(如发生异常)
4.2、责任链模式的实现
拦截器通过 递归调用 和 Handler
对象 形成责任链。每个拦截器接收一个 handler
参数,决定是否继续传递请求或直接返回。
-
关键对象 :
Handler
darttypedef Handler = Future<dynamic> Function(RequestOptions requestOptions);
-
执行流程:
- 创建一个初始
handler
,指向实际发送HTTP
请求的逻辑。 - 从最后一个拦截器开始,逆序 包裹每个拦截器的逻辑,形成链式调用。
- 创建一个初始
4.3、拦截器接口
拦截器接口定义了三个主要的方法:
onRequest
:在请求发送前
被调用。onResponse
:在响应接收后
被调用。onError
:在请求发生错误时
被调用。
可以通过实现这些方法来定义自己的拦截器逻辑。
dart
abstract class Interceptor {
Future<RequestOptions> onRequest(RequestOptions options, RequestInterceptorHandler handler);
Future<Response> onResponse(Response response, ResponseInterceptorHandler handler);
Future<void> onError(DioError err, ErrorInterceptorHandler handler);
}
4.4、拦截器处理器
拦截器处理器(InterceptorHandler
)允许拦截器在拦截过程中控制请求/响应的流程。它提供了两个主要的方法:
resolve
:用于解决请求/响应
,继续后续流程。reject
:用于拒绝请求/响应
,中断后续流程。
dart
class RequestInterceptorHandler {
Future<RequestOptions> resolve(RequestOptions options);
Future<void> reject(error, StackTrace? stackTrace]);
}
class ResponseInterceptorHandler {
Future<Response> resolve(Response response);
Future<void> reject(error, StackTrace? stackTrace]);
}
class ErrorInterceptorHandler {
Future<void> resolve(Response response);
Future<void> reject(error, StackTrace? stackTrace]);
}
4.5、拦截器链
Dio
实例维护了一个拦截器链,当请求发送或响应接收时,会依次调用链中的每个拦截器。拦截器链支持同步和异步拦截器,允许在拦截过程中进行复杂的异步操作。
执行流程如下:
- 请求发送前,依次调用每个拦截器的
onRequest
方法。 - 如果所有拦截器都通过(即调用
handler.resolve
),则发送请求。 - 请求发送后,接收响应,依次调用每个拦截器的
onResponse
方法。
如果在请求发送前或响应接收后发生错误,则依次调用每个拦截器的 onError
方法。
4.6、拦截器添加与管理
可以通过 dio.interceptors.add()
方法将自定义拦截器添加到拦截器链中。此外,Dio
还提供了一些内置的拦截器,如 LogInterceptor
用于自动记录请求和响应的日志。
dart
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 自定义请求拦截逻辑
return handler.next(options);
},
onResponse: (response, handler) {
// 自定义响应拦截逻辑
return handler.next(response);
},
onError: (err, handler) {
// 自定义错误拦截逻辑
return handler.next(err);
},
));
4.7、拦截器执行顺序
- 请求拦截器按添加顺序正向执行。
- 响应拦截器按添加顺序反向执行。
流程示例:
dart
// 请求 -> 拦截器A的onRequest -> 拦截器B的onRequest -> 发送请求
// 响应 -> 拦截器B的onResponse -> 拦截器A的onResponse -> 返回结果
五、设计哲学
5.1、单一职责原则(SRP
):精准的功能划分
每个拦截器仅处理 单一类型 的业务逻辑,避免功能耦合,确保代码可维护性 和复用性。
拦截器类型 | 职责描述 |
---|---|
LogInterceptor |
仅记录请求/响应日志,不涉及数据解析或修改。 |
CacheInterceptor |
仅处理缓存逻辑(如读取缓存、更新缓存),不干预其他流程。 |
RetryInterceptor |
仅实现重试机制(如网络异常自动重试),不处理认证或加密。 |
AuthInterceptor |
仅管理认证逻辑(如Token 刷新),不参与数据格式化或日志记录。 |
在拦截器基类 Interceptor
中,通过分离 onRequest
、onResponse
、onError
方法,强制开发者按职责拆分逻辑:
dart
abstract class Interceptor {
void onRequest(RequestOptions options, RequestInterceptorHandler handler);
void onResponse(Response response, ResponseInterceptorHandler handler);
void onError(DioException err, ErrorInterceptorHandler handler);
}
关键点:
- 每个方法仅处理对应阶段的逻辑(如
onRequest
仅处理请求前操作)。 - 开发者无法在一个方法中同时操作请求和响应,天然约束单一职责。
5.2、开闭原则(OCP
):无侵入式扩展
通过新增拦截器 而非修改已有代码来扩展功能,确保核心流程的稳定性。
dart
// 新增缓存拦截器(无需修改Dio源码)
dio.interceptors.add(CacheInterceptor());
// 新增性能监控拦截器
dio.interceptors.add(PerformanceInterceptor());
5.3、可插拔性:动态功能组合
通过 add
/remove
方法,可在运行时动态调整拦截器,实现功能模块的热插拔。
dart
// 开发环境:添加日志和Mock拦截器
if (isDev) {
dio.interceptors.add(LogInterceptor());
dio.interceptors.add(MockInterceptor());
}
// 生产环境:移除Mock,添加加密拦截器
if (isProd) {
dio.interceptors.removeWhere((i) => i is MockInterceptor);
dio.interceptors.add(EncryptInterceptor());
}
关键点:
- 动态调整拦截器链
不影响已发起的请求
,仅作用于后续请求
。 - 通过组合不同拦截器,可快速构建定制化网络层(如
调试模式
、安全模式
)。
5.4、设计优势总结
设计原则 | 实现手段 | 业务价值 |
---|---|---|
单一职责 | 拦截器功能隔离 + 接口强制拆分 | 代码可读性高,模块易维护、易测试。 |
开闭原则 | 动态扩展机制 | 功能扩展无需修改框架,降低升级风险。 |
可插拔性 | 动态增删拦截器 | 灵活适应多环境(开发、生产、测试)。 |
六、总结
拦截器的强大之处,在于将离散的网络处理逻辑转化为可编排的管道操作 。通过理解其责任链模式的内核,我们能像搭积木一样构建高可维护的网络层:
- 1、分层设计 :基础拦截器处理通用逻辑(如
日志
),业务拦截器处理领域需求(如Token
刷新)。 - 2、流程控制 :通过
handler.next()
、resolve()
、reject()
精确控制执行流。 - 3、系统化思维:将网络层视为由独立模块组成的生态系统,而非一坨面条代码。
当你能游刃有余地使用拦截器编排请求生命周期时,那些曾经让你头疼的全局状态管理
、多环境适配
问题,都将迎刃而解。优秀的架构不是设计出来的,而是通过拦截器这样的基础组件,逐步演化出来的
。
欢迎一键四连 (
关注
+点赞
+收藏
+评论
)