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

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

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

相关推荐
沅霖41 分钟前
Android: Handler 的用法详解
android
鸿蒙布道师1 小时前
鸿蒙NEXT开发数值工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
FlutterDevs2 小时前
Flutter 2025 年产品路线图发布
flutter
Yang-Never2 小时前
Open GL ES ->GLSurfaceView在正交投影下的图片旋转、缩放、位移
android·开发语言·kotlin·android studio·贴图
粤M温同学2 小时前
使用Android 原生LocationManager获取经纬度
android
stevenzqzq2 小时前
Android Hilt 教程
android
CYRUS_STUDIO3 小时前
Frida Stalker Trace 指令跟踪&寄存器变化监控
android·逆向·汇编语言
bst@微胖子3 小时前
Flutter之设计与主题&字体
android·flutter
Tee xm3 小时前
清晰易懂的 Flutter 开发环境搭建教程
linux·windows·flutter·macos·安装
深圳之光5 小时前
增加android 禁用相机后摄的接口
android·数码相机