Flutter dio插件的拦截器:实现请求与响应的统一处理
1. Flutter 与 Dio 插件简介
Flutter 是谷歌开发的一款开源 UI 框架,用于构建跨平台的原生应用。它采用 Dart 语言进行开发,能够让开发者使用一套代码库同时生成 iOS 和 Android 应用,大大提高了开发效率。
Dio 是 Flutter 生态中一款强大的 HTTP 请求库,它支持诸如拦截器、全局配置、FormData 上传等丰富功能。Dio 以其简洁易用且高效的特点,在 Flutter 开发中被广泛应用于处理网络请求,成为众多开发者的首选。
2. 为什么需要拦截器
在实际的应用开发中,我们常常需要对网络请求和响应进行一些统一的处理。例如,在每次请求前添加通用的请求头,如身份验证令牌(Token);在请求失败时进行统一的错误处理,或者在响应回来后对数据进行统一的解析等。如果在每个发起请求的地方都去手动添加这些逻辑,不仅代码冗余,而且后期维护成本高。
拦截器的出现很好地解决了这个问题。通过拦截器,我们可以在请求发出前和响应返回后,统一地执行特定的逻辑,实现代码的复用和优化,使网络请求部分的代码结构更加清晰。
3. Dio 拦截器基础
Dio 中的拦截器分为请求拦截器和响应拦截器。请求拦截器会在请求被发送之前被调用,我们可以在这个阶段对请求进行一些预处理,比如添加请求头、修改请求参数等。响应拦截器则是在收到服务器响应之后被调用,我们可以在这里对响应进行处理,例如数据解析、错误处理等。
3.1 添加拦截器
在 Dio 中添加拦截器非常简单。首先,我们需要引入 Dio 库:
import 'package:dio/dio.dart';
然后创建一个 Dio 实例,并添加拦截器:
void main() {
Dio dio = Dio();
// 添加请求拦截器
dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
print('请求被拦截,即将发送请求:$options');
return options;
},
));
// 添加响应拦截器
dio.interceptors.add(InterceptorsWrapper(
onResponse: (Response response) {
print('响应被拦截,收到响应:$response');
return response;
},
));
}
在上述代码中,InterceptorsWrapper
是 Dio 提供的一个拦截器包装类,我们通过它来定义具体的拦截逻辑。onRequest
回调函数用于处理请求拦截,onResponse
回调函数用于处理响应拦截。
3.2 请求拦截器的参数
onRequest
回调函数接收一个 RequestOptions
参数,RequestOptions
包含了请求的所有信息,例如请求的 URL、方法(GET、POST 等)、请求头、请求体等。我们可以通过修改 RequestOptions
的属性来修改请求的内容。
dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
// 添加通用请求头
options.headers['Authorization'] = 'Bearer your_token';
return options;
},
));
在这个例子中,我们在每个请求的请求头中添加了一个 Authorization
字段,值为 Bearer your_token
,这在需要身份验证的接口请求中非常常见。
3.3 响应拦截器的参数
onResponse
回调函数接收一个 Response
参数,Response
包含了服务器返回的响应信息,如状态码、响应头、响应体等。我们可以在这里对响应数据进行处理。
dio.interceptors.add(InterceptorsWrapper(
onResponse: (Response response) {
if (response.statusCode == 200) {
print('请求成功,响应数据:${response.data}');
} else {
print('请求失败,状态码:${response.statusCode}');
}
return response;
},
));
在这个例子中,我们根据响应的状态码来判断请求是否成功,并打印相应的信息。
4. 实现请求与响应的统一处理
4.1 统一添加请求头
在许多应用中,我们需要在每个请求中添加一些通用的请求头,如 Content - Type
、身份验证信息等。通过请求拦截器,我们可以很方便地实现这一点。
dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
options.headers['Content - Type'] = 'application/json';
// 假设从本地存储获取 Token
String? token = getTokenFromLocalStorage();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return options;
},
));
上述代码中,我们首先设置了 Content - Type
为 application/json
,表示请求体是 JSON 格式的数据。然后从本地存储获取用户的 Token,并添加到 Authorization
请求头中。这样,每个请求都会带上这些通用的请求头。
4.2 统一错误处理
在响应拦截器中,我们可以对请求失败的情况进行统一处理。常见的错误包括网络错误、服务器返回的错误状态码等。
dio.interceptors.add(InterceptorsWrapper(
onResponse: (Response response) {
if (response.statusCode! >= 400) {
// 处理 400 及以上的状态码错误
print('请求失败,状态码:${response.statusCode}');
showErrorDialog('请求失败,请稍后重试');
return Future.error( DioError(requestOptions: response.requestOptions, error: '请求失败,状态码:${response.statusCode}'));
}
return response;
},
onError: (DioError e) {
// 处理网络错误等其他错误
print('发生错误:${e.message}');
showErrorDialog('网络连接失败,请检查网络');
return Future.error(e);
},
));
在这段代码中,我们在 onResponse
回调中处理服务器返回的 400 及以上状态码的错误,弹出一个错误提示对话框,并返回一个错误的 Future
。在 onError
回调中处理网络连接等其他错误,同样弹出错误提示对话框。这样,所有的错误都能得到统一的处理,提升了用户体验。
4.3 统一数据解析
通常,服务器返回的数据需要进行一定的解析才能在应用中使用。我们可以在响应拦截器中统一进行数据解析。假设服务器返回的数据格式如下:
{
"code": 200,
"message": "成功",
"data": {
"name": "John",
"age": 30
}
}
我们可以在响应拦截器中这样解析数据:
dio.interceptors.add(InterceptorsWrapper(
onResponse: (Response response) {
if (response.statusCode == 200) {
Map<String, dynamic> data = response.data;
if (data['code'] == 200) {
return data['data'];
} else {
print('业务逻辑错误,错误信息:${data['message']}');
showErrorDialog(data['message']);
return Future.error( DioError(requestOptions: response.requestOptions, error: '业务逻辑错误,错误信息:${data['message']}'));
}
}
return response;
},
));
在这个例子中,我们首先判断状态码是否为 200,如果是,则进一步判断业务逻辑返回的 code
是否为 200。如果业务 code
为 200,就返回实际需要的数据部分(data
字段);否则,处理业务逻辑错误,弹出错误提示对话框并返回错误的 Future
。
5. 自定义拦截器类
虽然使用 InterceptorsWrapper
可以快速定义拦截器逻辑,但在实际项目中,为了代码的可维护性和复用性,我们通常会创建自定义的拦截器类。
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers['Content - Type'] = 'application/json';
String? token = getTokenFromLocalStorage();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.statusCode! >= 400) {
print('请求失败,状态码:${response.statusCode}');
showErrorDialog('请求失败,请稍后重试');
handler.reject( DioError(requestOptions: response.requestOptions, error: '请求失败,状态码:${response.statusCode}'));
}
handler.next(response);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
print('发生错误:${err.message}');
showErrorDialog('网络连接失败,请检查网络');
handler.reject(err);
}
}
在上述代码中,我们创建了一个 MyInterceptor
类,它继承自 Interceptor
类。然后重写了 onRequest
、onResponse
和 onError
方法来实现我们自定义的拦截逻辑。在使用时,我们可以这样添加拦截器:
Dio dio = Dio();
dio.interceptors.add(MyInterceptor());
这样,我们就可以将拦截器逻辑封装在一个类中,方便在不同的地方复用,并且使代码结构更加清晰。
6. 多个拦截器的执行顺序
当我们添加多个拦截器时,了解它们的执行顺序是很重要的。请求拦截器按照添加的顺序依次执行,而响应拦截器则按照添加顺序的相反顺序执行。
例如,我们添加了三个拦截器 Interceptor1
、Interceptor2
、Interceptor3
:
Dio dio = Dio();
dio.interceptors.add(Interceptor1());
dio.interceptors.add(Interceptor2());
dio.interceptors.add(Interceptor3());
在请求阶段,执行顺序为 Interceptor1.onRequest
-> Interceptor2.onRequest
-> Interceptor3.onRequest
。在响应阶段,执行顺序为 Interceptor3.onResponse
-> Interceptor2.onResponse
-> Interceptor1.onResponse
。如果某个拦截器在 onRequest
中返回了一个 Future.error
,那么后续的请求拦截器将不会执行,并且会直接进入 onError
回调。同样,如果在 onResponse
中返回了一个 Future.error
,后续的响应拦截器也不会执行。
7. 拦截器在复杂业务场景中的应用
7.1 重试机制
在网络不稳定的情况下,请求可能会偶尔失败。我们可以通过拦截器实现一个简单的重试机制。
class RetryInterceptor extends Interceptor {
int maxRetryCount = 3;
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.type == DioErrorType.connectTimeout || err.type == DioErrorType.receiveTimeout) {
int retryCount = 0;
while (retryCount < maxRetryCount) {
try {
Response response = await err.requestOptions.retry();
handler.resolve(response);
return;
} catch (e) {
retryCount++;
}
}
handler.reject(err);
} else {
handler.reject(err);
}
}
}
在上述代码中,RetryInterceptor
拦截器在捕获到连接超时或接收超时的错误时,会尝试重新发起请求,最多重试 maxRetryCount
次。如果重试成功,就通过 handler.resolve
返回响应;如果重试次数用尽仍失败,则通过 handler.reject
返回错误。
7.2 日志记录
在开发和调试过程中,记录网络请求和响应的详细信息是非常有帮助的。我们可以通过拦截器来实现日志记录功能。
class LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('请求 URL:${options.uri}');
print('请求方法:${options.method}');
print('请求头:${options.headers}');
if (options.data != null) {
print('请求体:${options.data}');
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('响应状态码:${response.statusCode}');
print('响应头:${response.headers}');
print('响应体:${response.data}');
handler.next(response);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
print('错误信息:${err.message}');
handler.reject(err);
}
}
LoggingInterceptor
拦截器在请求、响应和错误发生时,分别打印出相关的详细信息,帮助开发者快速定位问题。
8. 注意事项
- 内存泄漏:在拦截器中,如果使用了一些需要手动释放资源的对象,如流(Stream)、定时器(Timer)等,一定要注意在适当的时候释放资源,避免内存泄漏。
- 性能问题:虽然拦截器可以方便地统一处理请求和响应,但过多的拦截器或者复杂的拦截逻辑可能会影响性能。尽量保持拦截器逻辑的简洁,避免在拦截器中执行耗时操作。
- 版本兼容性:Dio 库可能会随着时间推移进行更新,新的版本可能会对拦截器的 API 有一些改动。在升级 Dio 版本时,要注意检查拦截器代码是否仍然兼容。
通过合理使用 Dio 的拦截器,我们可以有效地实现请求与响应的统一处理,提高代码的复用性和可维护性,打造出更加健壮和高效的 Flutter 应用。无论是简单的通用请求头添加,还是复杂的重试机制和日志记录,拦截器都能为我们提供强大的支持。在实际开发中,根据具体的业务需求,灵活运用拦截器,将会极大地提升开发效率和应用质量。