2、Dio库入门:Dio的安装与配置、基本GET/POST请求、请求与响应拦截器初探
2.1 为什么选Dio?
在Flutter的网络请求领域,Dio几乎是事实标准。我刚开始做Flutter项目时,也试过用原生的http包,但很快就发现一个问题——代码重复率太高了。每个接口都要写请求头、处理超时、解析错误码……说白了,就是体力活。
Dio帮我们把这些脏活累活都干了。它提供了拦截器、全局配置、文件上传下载、请求取消等能力。你想想看,一个App里少说几十个接口,如果每个都单独配置,那维护成本得多高?
2.2 安装与配置
安装Dio很简单。在pubspec.yaml里加上依赖就行:
dependencies:
dio: ^5.4.0
然后执行flutter pub get。嗯,这里要注意版本号,我建议用最新的稳定版,别追太新的beta版,容易踩坑。
接下来是初始化。我个人习惯把Dio实例封装成一个单例,这样全局共享一套配置:
class HttpClient {
late final Dio _dio;
HttpClient._internal() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
}
static final HttpClient _instance = HttpClient._internal();
factory HttpClient() => _instance;
Dio get dio => _dio;
}
这里有几个关键配置:
- baseUrl:基础地址,后面写接口路径时只用写相对路径
- connectTimeout:连接超时,我一般设10秒
- receiveTimeout:接收超时,也是10秒
- headers:公共请求头,比如Content-Type
2.3 基本GET请求
GET请求是最常用的。比如获取用户列表:
Future<List<User>> getUsers() async {
try {
final response = await HttpClient().dio.get('/users');
if (response.statusCode == 200) {
final List<dynamic> data = response.data;
return data.map((json) => User.fromJson(json)).toList();
}
throw Exception('请求失败: ${response.statusCode}');
} on DioException catch (e) {
// 这里处理网络异常
throw Exception('网络错误: ${e.message}');
}
}
带参数的GET请求也很常见:
final response = await dio.get(
'/users',
queryParameters: {'page': 1, 'size': 20},
);
你看,Dio会自动把queryParameters拼接到URL后面,省得我们自己拼字符串了。
2.4 基本POST请求
POST请求用于提交数据。比如创建新用户:
Future<User> createUser(String name, String email) async {
try {
final response = await dio.post(
'/users',
data: {
'name': name,
'email': email,
},
);
return User.fromJson(response.data);
} on DioException catch (e) {
throw Exception('创建用户失败: ${e.message}');
}
}
这里有个细节——data参数传Map,Dio会自动序列化成JSON。如果你传的是自定义对象,记得先调用toJson()方法。
application/json。如果你要传表单数据,需要手动设置:options: Options(contentType: 'application/x-www-form-urlencoded')。
2.5 请求拦截器初探
拦截器是Dio最强大的功能之一。它允许你在请求发出前或响应返回后插入自定义逻辑。
先看请求拦截器。我经常用它来统一添加Token:
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 从本地存储获取Token
final token = LocalStorage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// 打印请求日志
print('请求路径: ${options.path}');
print('请求参数: ${options.queryParameters}');
handler.next(options);
},
));
为什么要用拦截器?你想想看,如果每个接口都手动加Token,万一哪天Token的key变了,你得改多少个地方?用拦截器就改一处,省心。
我曾经接手过一个老项目,里面每个请求都手动拼Token,结果后端把Authorization改成了token字段……嗯,那两天我改代码改到想吐。
2.6 响应拦截器初探
响应拦截器用来统一处理返回数据。比如全局错误码处理:
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
// 假设后端返回格式: { code: 0, data: {}, message: '成功' }
final code = response.data['code'];
if (code != 0) {
// 业务错误,抛出异常
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: response.data['message'] ?? '未知错误',
);
}
// 只返回data部分
response.data = response.data['data'];
handler.next(response);
},
onError: (error, handler) {
// 统一处理网络错误
if (error.type == DioExceptionType.connectionTimeout) {
// 连接超时,提示用户
print('连接超时,请检查网络');
} else if (error.type == DioExceptionType.badResponse) {
// HTTP状态码错误
print('服务器错误: ${error.response?.statusCode}');
}
handler.next(error);
},
));
这样做的好处很明显——业务代码里不用再写一堆if-else判断错误码了。拦截器帮你过滤干净,业务层拿到的就是干净的数据。
2.7 拦截器的执行顺序
多个拦截器是按添加顺序执行的。请求时从上到下,响应时从下到上。说白了就是先进先出:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 添加Token拦截器 | 请求前注入Token |
| 2 | 添加日志拦截器 | 请求前打印日志 |
| 3 | 发送请求 | 实际网络请求 |
| 4 | 日志拦截器响应 | 响应后打印结果 |
| 5 | Token拦截器响应 | 响应后处理Token刷新 |
这个顺序很重要。比如你想在日志里看到完整的请求信息,那日志拦截器就得加在Token拦截器后面,这样打印时Token已经注入进去了。
2.8 小结
这一章我们走通了Dio的安装配置、基本请求和拦截器入门。说实话,这些只是Dio的冰山一角。但有了这个基础,后面讲请求取消、文件上传、Cookie管理这些高级功能时,你就不会觉得陌生了。
下一章我们会深入拦截器的进阶用法——比如自动刷新Token、请求重试、缓存策略。这些才是真正提升开发效率的利器。