系统化掌握Dart网络编程之Dio(四):拦截器篇

前言

在移动应用开发中,网络请求 如同血管中的血液,承载着数据交互的生命力。然而,你是否遇到过这样的场景:每个请求都要手动添加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); // 继续传递
  }
}

③、上下文隔离

每个拦截器仅能访问当前阶段的请求/响应对象RequestOptionsResponse),无法直接修改其他拦截器的内部状态,保证职责单一性


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);  
}  

②、RequestInterceptorHandlerResponseInterceptorHandlerErrorInterceptorHandler:控制类

用于控制拦截器链的传递流程,核心方法:

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(),否则链式调用会中断。

  • 修改 RequestOptionsResponse 时需注意对象不可变性,推荐使用 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

    dart 复制代码
    typedef 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 中,通过分离 onRequestonResponseonError 方法,强制开发者按职责拆分逻辑:

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、系统化思维:将网络层视为由独立模块组成的生态系统,而非一坨面条代码。

当你能游刃有余地使用拦截器编排请求生命周期时,那些曾经让你头疼的全局状态管理多环境适配问题,都将迎刃而解。优秀的架构不是设计出来的,而是通过拦截器这样的基础组件,逐步演化出来的

欢迎一键四连关注 + 点赞 + 收藏 + 评论

相关推荐
openinstall1 分钟前
A/B测试如何借力openinstall实现用户价值深挖?
android·ios·html
二流小码农3 分钟前
鸿蒙开发:资讯项目实战之项目初始化搭建
android·ios·harmonyos
志旭1 小时前
android15 vsync源码分析
android
志旭1 小时前
Android 14 HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia
android
whysqwhw2 小时前
Egloo 高级用法
android
whysqwhw2 小时前
Egloo 架构设计
android
whysqwhw2 小时前
Egloo 快速入门指南
android
吴敬悦2 小时前
在 Flutter 中集成 C/C++ 代码 BLE LC3( 基于 AI 教程 )
flutter·ai编程
雨白3 小时前
精通 Android 通知
android
用户2018792831674 小时前
搜索算法故事:从图书馆找书到迷宫探险
android