拦截器是什么
Dio 的拦截器(Interceptor)是 在请求生命周期中的钩子函数。你可以在 请求发出前、响应返回时、错误发生时 对数据进行处理。
拦截器生命周期
Request 发起
│
▼
onRequest 拦截器
│ ├─ next() → 继续请求
│ ├─ resolve() → 返回自定义响应
│ └─ reject() → 进入 onError, ensureForward: true
▼
请求真正发出 (HTTP)
│
▼
onResponse 拦截器
│ ├─ next() → 返回给调用方
│ ├─ resolve() → 返回自定义响应
│ └─ reject() → 进入 onError , ensureForward: true
▼
返回给调用方
│
▼
onError 拦截器 (请求失败 / reject)
│ ├─ next() → 错误继续传递
│ ├─ resolve() → 转换为正常响应
│ └─ reject() → 抛出异常,直接终止链路
三大核心方法
onRequest
触发时机:请求发出前
常见用途:
- 添加 公共 header(如 token)
- 打印日志
- mock 数据(用
resolve
返回)
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print("请求拦截: ${options.uri}");
options.headers["Authorization"] = "Bearer token";
handler.next(options); // 放行
}
onResponse
触发时机:收到响应时
常见用途:
- 统一处理响应格式(比如后端返回
{"code":0,"data":{...}}
) - 打印响应日志
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print("响应拦截: ${response.data}");
handler.next(response); // 继续传递
}
onError
- 触发时机:请求失败 / 主动
reject
- 常见用途:
- 统一错误处理
- 自动刷新 token 后重试请求
- 记录日志
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print("错误拦截: ${err.message}");
handler.next(err); // 把错误传递给调用方
}
三个控制方法
方法 | 作用 | 典型场景 |
---|---|---|
next() |
放行,继续执行下一个拦截器或 Dio 自身逻辑 | 日志记录、轻量修改 |
resolve() |
返回一个自定义响应,拦截后续逻辑 | Mock 数据、缓存 |
reject() |
抛出一个错误,中断请求 | 参数校验、权限检查 |
handler.next
//onRequest
void next(RequestOptions requestOptions) {
// onResponse
void next(Response response) {
// onError
void next(DioException error)
作用:把当前拦截的内容 交给下一个拦截器继续处理。
使用场景:你只是对请求/响应做一些记录(比如打印日志),并且还希望后续拦截器或 Dio 自己继续处理。
handler.resolve
//onRequest
void resolve(
Response response, [
bool callFollowingResponseInterceptor = false,
]) {
// onResponse
void resolve(Response response) {
// onError
void resolve(Response response) {
作用:直接返回一个响应,跳过后续的拦截器和真正的请求过程。
使用场景:
- 你想 提前返回一个模拟结果(mock),不让请求真的发出去。
- 或者在某些情况下,直接构造一个“缓存响应”。
在 onRequest
里调用 resolve()
时,有个额外的布尔参数callFollowingResponseInterceptor
,它的作用是:控制 当前返回的响应,是否还要经过后续的 response 拦截器(onResponse)。
- 默认值:false:表示你在
onRequest
中直接resolve
一个Response
,不会再走任何后续的 response 拦截器。响应会直接返回给调用者。 - 如果设为 true:你在
onRequest
阶段就“伪造”了一个响应,但依然会让它走一遍onResponse
拦截器链路。这样可以让 response 拦截器继续对它做加工或日志记录。
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 假设我们拦截所有 /mock 请求
if (options.path.contains("/mock")) {
final fakeResponse = Response(
requestOptions: options,
data: {"msg": "这是一个伪造的响应"},
statusCode: 200,
);
// 如果传 true,那么后续的 onResponse 也会触发
return handler.resolve(fakeResponse, true);
}
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print("onResponse 收到: ${response.data}");
super.onResponse(response, handler);
}
}
流程对比:
false: onRequest → resolve(fakeResponse) → 直接返回给调用者
true: onRequest → resolve(fakeResponse, true) → 进入 onResponse → 返回给调用者
handler.reject
//onRequest
void reject(
DioException error, [
bool callFollowingErrorInterceptor = false,
]) {
// onResponse
void reject(
DioException error, [
bool callFollowingErrorInterceptor = false,
]) {
// onError
void reject(DioException error) {
作用:直接抛出一个错误,跳过后续流程。
使用场景:你在请求前发现问题(比如没有 Token,或者参数错误),可以直接中断请求并抛出错误。
callFollowingErrorInterceptor
的作用是:控制当前你 手动抛出的错误,是否还要继续交给后续的 错误拦截器(onError) 处理。
- 默认值:false:错误会立即抛出给调用方(
await dio.get()
那里),不会触发其它onError
拦截器。 - 如果设为 true,错误会像“正常发生的错误”一样,继续进入拦截器链路,触发后续的
onError
。
在onError
里面没有这个配置,默认是直接抛异常出来,不用走onError
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (options.path.contains("error")) {
return handler.reject(
DioException(
requestOptions: options,
error: "拦截器主动拒绝请求",
type: DioExceptionType.badResponse,
),
true, // 👈 让错误进入 onError
);
}
super.onRequest(options, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print("onError 拦截到了: ${err.error}");
super.onError(err, handler);
}
}
流程对比:
false: onRequest → reject(error) → 直接抛出异常给调用者
true: onRequest → reject(error, true) → 进入 onError → 再抛给调用者
两种拦截器添加方法
匿名拦截器(InterceptorsWrapper)
final dio = Dio();
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
print("➡️ 请求拦截: ${options.uri}");
// 继续请求
handler.next(options);
},
onResponse: (response, handler) {
print("✅ 响应拦截: ${response.data}");
handler.next(response);
},
onError: (DioException e, handler) {
print("❌ 错误拦截: ${e.message}");
handler.next(e);
},
),
);
- 写法简洁,适合简单逻辑(如打印日志、统一 token 处理)。
- 适合临时性、局部性逻辑。
- 缺点是逻辑分散在一堆闭包里,不利于大型项目管理。
自定义拦截器类(继承 Interceptor
)
// 定义拦截器
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print("➡️ 请求拦截: ${options.uri}");
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print("✅ 响应拦截: ${response.data}");
super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print("❌ 错误拦截: ${err.message}");
super.onError(err, handler);
}
}
// 注册时:
final dio = Dio();
dio.interceptors.add(MyInterceptor());
- 逻辑清晰,结构化,适合大项目。
- 可以定义多个拦截器类,专门负责鉴权 / 缓存 / 日志 / 错误处理等。
- 可复用,团队协作更好。
三个参数
RequestOptions
在 Dio 里,RequestOptions
是 请求的完整配置信息,每次发请求都会生成一个 RequestOptions
对象。
RequestOptions
常见属性如下:
请求基本信息
method
→ 请求方法,如 "GET"
, "POST"
path
→ 请求路径(相对或绝对)
baseUrl
→ 基础地址,和 path
拼接成完整 URL
uri
→ baseUrl + path
合并之后的最终 Uri
对象
请求参数与数据
queryParameters
→ GET 请求的参数(会拼到 URL 后面)
data
→ POST/PUT 的请求体数据,可以是 Map
、FormData
、String
等
headers
→ 请求头(Map<String, dynamic>
)
其他配置
extra
→ 自定义字段(开发者可存放标记、缓存策略等信息)
contentType
→ application/json
/ multipart/form-data
等
responseType
→ 响应的数据格式,比如 ResponseType.json
、ResponseType.bytes
、ResponseType.stream
followRedirects
→ 是否跟随重定向
connectTimeout
→ 连接超时时间
sendTimeout
→ 发送数据超时时间
receiveTimeout
→ 接收数据超时时间
内部控制
cancelToken
→ 请求取消用的 token
validateStatus
→ 自定义状态码校验函数
onReceiveProgress
→ 下载进度回调
onSendProgress
→ 上传进度回调
Response
在 Dio 里,Response
表示一次请求的结果,包含了 服务端返回的数据 + 请求的上下文信息。
核心数据
data
→ 响应的数据(可能是 Map
/ List
/ String
/ Uint8List
等)例如:JSON API 会被自动解析成 Map<String, dynamic>
。
HTTP 相关
statusCode
→ HTTP 状态码(如 200
, 404
, 500
)
statusMessage
→ 状态消息(如 "OK"
, "Not Found"
)
请求上下文
headers
→ 响应头(Headers
对象,可用 response.headers.value("Content-Type")
获取单个字段)
requestOptions
→ 发起这次请求时的 RequestOptions
(方便调试和回溯请求参数)
额外信息
extra
→ 存放自定义的扩展字段(一般用于拦截器加工数据)
isRedirect
→ 是否发生了重定向
redirects
→ 重定向链路(List<RedirectInfo>
)
DioException
在 Dio 中,所有请求错误都会被包装成 DioException
,这样开发者就能统一处理,而不用区分底层是 SocketException
、TimeoutException
还是 HttpException
。
DioException
的核心属性如下:
class DioException implements Exception {
final DioExceptionType type; // 错误类型(超时、响应错误等)
final String? message; // 错误信息(可读的文字描述)
final dynamic error; // 原始错误对象(通常是系统异常)
final RequestOptions requestOptions; // 请求的相关信息
final Response? response; // 服务端返回的响应(如果有)
final StackTrace? stackTrace; // 堆栈信息
}
DioExceptionType
**type
(DioExceptionType
)**错误类型枚举,常见的有:
connectionTimeout
→ 连接超时sendTimeout
→ 发送数据超时receiveTimeout
→ 接收数据超时badResponse
→ 服务器返回了错误状态码(4xx / 5xx)cancel
→ 请求被取消unknown
→ 未知错误
message
**message
(String?
)**对错误的文字描述。比如 "Http status error [404]"
。
RequestOptions
**requestOptions
(RequestOptions
)**请求相关的详细信息。包括:
path
method
headers
data
(请求体)queryParameters
等。
Response
**response
(Response?
)**如果服务端有返回(即使是错误,比如 404
),这个属性会包含 Response
对象。可以从 response?.statusCode
或 response?.data
中拿到服务端返回的内容。
stackTrace
stackTrace
(StackTrace?
):报错时的调用堆栈,便于调试。
案例:自定义拦截器
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dio Interceptor Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
/// 自定义拦截器
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
debugPrint("-------------拦截到请求---------------");
debugPrint("➡️ 请求: ${options.uri}");
if (options.path.contains("mock")) {
// resolve: 提前返回一个假数据
return handler.resolve(
Response(
requestOptions: options,
data: {"message": "这是拦截器返回的假数据"},
statusCode: 200,
),
);
}
if (options.path.contains("error")) {
// reject: 主动抛出一个错误
return handler.reject(
DioException(
requestOptions: options,
error: "拦截器主动拒绝请求",
type: DioExceptionType.badResponse,
),
true, // ✅ 确保进入 onError
);
}
// next: 正常放行
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
debugPrint("-------------拦截到响应---------------");
debugPrint("✅ 响应: ${response.data}");
// 放行,交给下一个或返回给调用方
super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
debugPrint("-------------拦截到错误---------------");
debugPrint("❌ 错误: ${err.error}");
handler.resolve(
Response(
requestOptions: err.requestOptions,
data: {"message": "错误已被拦截器处理"},
statusCode: 200,
),
// 如果你、在 onError 中用 reject,
//调用方就会直接在 try-catch 里捕获异常,
//而不会再经过别的拦截器处理。
// handler.reject(
// DioException(
// requestOptions: err.requestOptions,
// error: "拦截器主动拒绝请求",
// type: DioExceptionType.badResponse,
// ),
);
}
}
class _HomePageState extends State<HomePage> {
final Dio _dio = Dio();
@override
void initState() {
super.initState();
_dio.interceptors.add(MyInterceptor());
}
Future<void> _testNormalRequest() async {
debugPrint("--------------发起正常请求---------------");
final res = await _dio.get("https://jsonplaceholder.typicode.com/todos/1");
debugPrint("最终结果: ${res.data}");
}
Future<void> _testMockRequest() async {
debugPrint("--------------发起模拟请求---------------");
final res = await _dio.get("mock");
debugPrint("最终结果: ${res.data}");
}
Future<void> _testErrorRequest() async {
debugPrint("--------------发起错误请求---------------");
final res = await _dio.get("error");
debugPrint("最终结果: ${res.data}");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Dio Interceptor Demo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _testNormalRequest,
child: const Text("正常请求 (next)"),
),
ElevatedButton(
onPressed: _testMockRequest,
child: const Text("模拟请求 (resolve)"),
),
ElevatedButton(
onPressed: _testErrorRequest,
child: const Text("错误请求 (reject+resolve)"),
),
],
),
),
);
}
}
以下是日志:
I/flutter (20355): --------------发起正常请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: https://jsonplaceholder.typicode.com/todos/1
I/flutter (20355): -------------拦截到错误---------------
I/flutter (20355): ❌ 错误: null
I/flutter (20355): 最终结果: {message: 错误已被拦截器处理}
I/flutter (20355): --------------发起模拟请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: mock
I/flutter (20355): 最终结果: {message: 这是拦截器返回的假数据}
I/flutter (20355): --------------发起错误请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: error
I/flutter (20355): -------------拦截到错误---------------
I/flutter (20355): ❌ 错误: 拦截器主动拒绝请求
I/flutter (20355): 最终结果: {message: 错误已被拦截器处理}
多个拦截器
Dio 拦截器执行规则
在 Dio 里,拦截器类似一个「洋葱模型」:
- 请求阶段(onRequest):按添加顺序执行
- 响应阶段(onResponse):按添加顺序的反向执行
- 错误阶段(onError):同样是反向执行
也就是:
- Request 顺序:Interceptor1 → Interceptor2 → …
- Response / Error 顺序:… → Interceptor2 → Interceptor1

请求成功(返回 200)
sequenceDiagram participant App as 应用 participant I1 as 拦截器1 participant I2 as 拦截器2 participant Server as 服务器 App->>I1: onRequest I1->>I2: onRequest I2->>Server: 发起请求 Server-->>I2: 返回响应 I2-->>I1: onResponse I1-->>App: onResponse
请求失败(返回 404)
sequenceDiagram participant App as 应用 participant I1 as 拦截器1 participant I2 as 拦截器2 participant Server as 服务器 App->>I1: onRequest I1->>I2: onRequest I2->>Server: 发起请求 Server-->>I2: 返回错误 I2-->>I1: onError I1-->>App: onError
案例:两个拦截器
import 'package:dio/dio.dart';
void main() async {
final dio = Dio();
// 拦截器1
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print("拦截器1 - 请求开始");
handler.next(options); // 继续传递
},
onResponse: (response, handler) {
print("拦截器1 - 收到响应");
handler.next(response);
},
onError: (e, handler) {
print("拦截器1 - 捕获错误");
handler.next(e);
},
));
// 拦截器2
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print("拦截器2 - 请求开始");
handler.next(options);
},
onResponse: (response, handler) {
print("拦截器2 - 收到响应");
handler.next(response);
},
onError: (e, handler) {
print("拦截器2 - 捕获错误");
handler.next(e);
},
));
try {
// 这里随便找个可能返回404的地址
final response = await dio.get("https://httpbin.org/status/200");
print("最终响应: ${response.statusCode}");
} catch (e) {
print("最终捕获异常: $e");
}
}
以下是日志:
I/flutter (21319): 拦截器1 - 请求开始
I/flutter (21319): 拦截器2 - 请求开始
D/ProfileInstaller(21319): Installing profile for com.example.flutter_sophomore
I/flutter (21319): 拦截器1 - 收到响应
I/flutter (21319): 拦截器2 - 收到响应
I/flutter (21319): 最终响应: 200
拦截器常见应用场景
统一添加 Token
// 在请求前统一添加Token
onRequest(options, handler) {
options.headers["Authorization"] = "Bearer xxx";
handler.next(options);
}
统一处理响应格式
onResponse(response, handler) {
if (response.data["code"] != 0) {
return handler.reject(DioException(
requestOptions: response.requestOptions,
error: response.data["message"],
), true);
}
response.data = response.data["data"]; // 直接提取 data
// 或者把data转成其他格式
handler.next(response);
}
统一错误处理
onError(err, handler) {
if (err.type == DioExceptionType.connectionTimeout) {
print("网络超时");
} else {
print("其他错误: ${err.message}");
}
handler.next(err);
}
刷新 Token 重试请求
onError(err, handler) async {
if (err.response?.statusCode == 401) {
// 假设 refreshToken() 能刷新 token
final newToken = await refreshToken();
err.requestOptions.headers["Authorization"] = "Bearer $newToken";
final cloneReq = await dio.fetch(err.requestOptions); // 重新发起请求
return handler.resolve(cloneReq); // 用新的结果返回
}
handler.next(err);
}