Dio 是 Flutter 中最强大、最流行的 Dart HTTP 客户端库,提供了拦截器、全局配置、FormData、文件上传/下载、请求取消、超时等高级功能。
1. 安装与初始化
1.1 添加依赖
在 pubspec.yaml 文件中添加 dio 依赖:
dependencies:
dio: ^5.4.1 # 请使用最新版本
运行 flutter pub get 安装依赖。
1.2 创建 Dio 实例
import 'package:dio/dio.dart';
// 方法一:创建实例时配置
Dio dio = Dio(
BaseOptions(
baseUrl: "https://api.example.com",
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
},
),
);
// 方法二:创建后配置
Dio dio = Dio();
void configureDio() {
dio.options.baseUrl = 'https://api.example.com';
dio.options.connectTimeout = Duration(seconds: 5);
dio.options.receiveTimeout = Duration(seconds: 3);
}
建议:在项目中通常使用单例模式管理 Dio 实例。
2. 发起 HTTP 请求
2.1 GET 请求
try {
// 方式一:查询参数拼接在URL中
Response response = await dio.get("/user?id=123");
print(response.data);
// 方式二:使用 queryParameters 参数(推荐)
Response response2 = await dio.get(
"/test",
queryParameters: {'id': 12, 'name': 'dio'},
);
print(response2.data.toString());
} on DioException catch (e) {
print(e.message);
}
2.2 POST 请求
try {
// 发送 JSON 数据
Response response = await dio.post(
"/user",
data: {'name': 'John', 'age': 25},
);
// 发送 FormData
FormData formData = FormData.fromMap({
'name': 'dio',
'date': DateTime.now().toIso8601String(),
});
Response formResponse = await dio.post('/info', data: formData);
print(response.data);
} on DioException catch (e) {
print(e.message);
}
2.3 其他请求方法
// PUT 请求 - 更新资源
await dio.put("/user/123", data: {"name": "john doe"});
// DELETE 请求 - 删除资源
await dio.delete("/user/123");
// PATCH 请求 - 部分更新资源
await dio.patch("/user/123", data: {"name": "johnny"});
// HEAD 请求 - 获取头部信息
Response headResponse = await dio.head("/user/123");
print(headResponse.headers);
// OPTIONS 请求 - 获取通信选项
Response optionsResponse = await dio.options("/user/123");
3. 响应处理
3.1 响应数据结构
Response response = await dio.get('https://api.example.com/user');
print(response.data); // 响应体(可能已被转换)
print(response.statusCode); // 状态码
print(response.headers); // 响应头
print(response.requestOptions); // 请求信息
print(response.statusMessage); // 状态消息
// 获取流式响应
final streamResponse = await dio.get(
url,
options: Options(responseType: ResponseType.stream),
);
print(streamResponse.data.stream);
// 获取字节响应
final bytesResponse = await dio.get<List<int>>(
url,
options: Options(responseType: ResponseType.bytes),
);
print(bytesResponse.data); // List<int>
3.2 与 Flutter UI 集成
import 'package:flutter/material.dart';
class UserList extends StatelessWidget {
Future<List<User>> fetchUsers() async {
final response = await dio.get('/users');
List<dynamic> jsonList = response.data;
return jsonList.map((json) => User.fromJson(json)).toList();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<User>>(
future: fetchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
User user = snapshot.data![index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
}
},
);
}
}
4. 错误处理
4.1 DioException 类型(新版本)
Dio 5.x 使用 DioException 替代旧的 DioError:
try {
Response response = await dio.get("/user?id=123");
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
print('连接超时');
break;
case DioExceptionType.sendTimeout:
print('发送超时');
break;
case DioExceptionType.receiveTimeout:
print('接收超时');
break;
case DioExceptionType.badResponse:
print('服务器错误,状态码:${e.response?.statusCode}');
print('响应数据:${e.response?.data}');
break;
case DioExceptionType.cancel:
print('请求被取消');
break;
case DioExceptionType.connectionError:
print('连接错误,请检查网络');
break;
case DioExceptionType.badCertificate:
print('证书验证失败');
break;
case DioExceptionType.unknown:
default:
print('未知错误: ${e.message}');
break;
}
}
4.2 错误类型说明
-
connectionTimeout:连接服务器超时
-
sendTimeout:数据发送超时
-
receiveTimeout:接收响应超时
-
badResponse:服务器返回错误状态码(4xx、5xx)
-
cancel:请求被取消
-
connectionError:网络连接问题
-
badCertificate:HTTPS 证书验证失败
-
unknown:其他未知错误
5. 拦截器(Interceptors)
拦截器是 Dio 最强大的功能之一,允许在请求/响应流程中插入处理逻辑。
5.1 基础拦截器
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
// 请求前处理
print('发送请求: ${options.uri}');
// 添加认证token
options.headers['Authorization'] = 'Bearer your_token_here';
return handler.next(options); // 继续请求
},
onResponse: (Response response, ResponseInterceptorHandler handler) {
// 响应后处理
print('收到响应: ${response.statusCode}');
return handler.next(response);
},
onError: (DioException error, ErrorInterceptorHandler handler) {
// 错误处理
print('请求错误: ${error.type}');
return handler.next(error);
},
),
);
5.2 实用拦截器示例
// 1. 日志拦截器
class LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('REQUEST[${options.method}] => PATH: ${options.path}');
print('Headers: ${options.headers}');
if (options.data != null) {
print('Body: ${options.data}');
}
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
print('Data: ${response.data}');
super.onResponse(response, handler);
}
}
// 2. Token 刷新拦截器
class TokenRefreshInterceptor extends Interceptor {
final Dio _tokenDio = Dio();
bool _isRefreshing = false;
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 && !_isRefreshing) {
_isRefreshing = true;
try {
// 刷新token
await refreshToken();
// 重试原始请求
final response = await dio.request(
err.requestOptions.path,
data: err.requestOptions.data,
queryParameters: err.requestOptions.queryParameters,
options: Options(
method: err.requestOptions.method,
headers: err.requestOptions.headers,
),
);
handler.resolve(response);
} catch (e) {
handler.reject(err);
} finally {
_isRefreshing = false;
}
} else {
handler.next(err);
}
}
}
6. 文件上传与下载
6.1 单文件上传
FormData formData = FormData.fromMap({
'name': '文件名',
'file': await MultipartFile.fromFile(
'./text.txt',
filename: 'upload.txt',
),
});
Response response = await dio.post(
'/upload',
data: formData,
onSendProgress: (int sent, int total) {
print('上传进度: $sent / $total');
},
);
6.2 多文件上传
FormData formData = FormData.fromMap({
'name': 'dio',
'files': [
await MultipartFile.fromFile('./text1.txt', filename: 'text1.txt'),
await MultipartFile.fromFile('./text2.txt', filename: 'text2.txt'),
await MultipartFile.fromFile('./text3.txt', filename: 'text3.txt'),
]
});
Response response = await dio.post('/upload-multiple', data: formData);
6.3 文件下载
// 获取应用临时目录
import 'package:path_provider/path_provider.dart';
void downloadFile() async {
// 获取存储路径
Directory tempDir = await getTemporaryDirectory();
String savePath = '${tempDir.path}/filename.pdf';
CancelToken cancelToken = CancelToken();
try {
await dio.download(
'https://example.com/file.pdf',
savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
double progress = (received / total) * 100;
print('下载进度: ${progress.toStringAsFixed(2)}%');
}
},
cancelToken: cancelToken,
deleteOnError: true, // 下载出错时删除部分文件
);
print('下载完成: $savePath');
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('下载已取消');
} else {
print('下载失败: ${e.message}');
}
}
}
// 取消下载
void cancelDownload() {
cancelToken.cancel('用户取消下载');
}
7. 高级配置
7.1 请求选项(Options)
Response response = await dio.get(
'/data',
options: Options(
headers: {'custom-header': 'value'},
responseType: ResponseType.json,
contentType: 'application/json',
sendTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
extra: {'custom_info': '可以后续在拦截器中获取'}, // 自定义字段
validateStatus: (status) {
// 自定义状态码验证逻辑
return status! < 500; // 只认为500以下的状态码是成功的
},
),
);
7.2 请求取消
CancelToken cancelToken = CancelToken();
// 发起可取消的请求
Future<void> fetchData() async {
try {
Response response = await dio.get(
'/large-data',
cancelToken: cancelToken,
);
print(response.data);
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('请求被取消');
}
}
}
// 取消请求
void cancelRequest() {
cancelToken.cancel('用户取消操作');
}
// 在组件销毁时取消请求(防止内存泄漏)
@override
void dispose() {
cancelToken.cancel('组件销毁');
super.dispose();
}
7.3 并发请求
// 同时发起多个请求
Future<void> fetchMultipleData() async {
try {
List<Response> responses = await Future.wait([
dio.get('/user/1'),
dio.get('/user/2'),
dio.get('/user/3'),
]);
for (var response in responses) {
print('用户数据: ${response.data}');
}
} on DioException catch (e) {
print('请求失败: ${e.message}');
}
}
8. 项目实战:封装 Dio 服务
8.1 基础封装示例
import 'package:dio/dio.dart';
class HttpService {
static final HttpService _instance = HttpService._internal();
late Dio _dio;
factory HttpService() => _instance;
HttpService._internal() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
));
// 添加拦截器
_dio.interceptors.add(LoggingInterceptor());
_dio.interceptors.add(TokenInterceptor());
}
// GET 请求
Future<Response> get(String path, {Map<String, dynamic>? queryParams}) async {
try {
return await _dio.get(
path,
queryParameters: queryParams,
);
} on DioException catch (e) {
_handleError(e);
rethrow;
}
}
// POST 请求
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} on DioException catch (e) {
_handleError(e);
rethrow;
}
}
// 错误处理
void _handleError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
throw Exception('连接超时,请检查网络');
case DioExceptionType.badResponse:
if (e.response?.statusCode == 401) {
throw Exception('身份验证失败,请重新登录');
} else if (e.response?.statusCode == 404) {
throw Exception('请求的资源不存在');
} else {
throw Exception('服务器错误: ${e.response?.statusCode}');
}
case DioExceptionType.connectionError:
throw Exception('网络连接失败,请检查网络设置');
default:
throw Exception('网络请求失败: ${e.message}');
}
}
}
// 使用示例
final http = HttpService();
User user = await http.get('/user/1');
8.2 结合状态管理的完整示例
// api_service.dart
class ApiService {
final Dio _dio;
ApiService({required String baseUrl})
: _dio = Dio(BaseOptions(baseUrl: baseUrl)) {
_setupInterceptors();
}
void _setupInterceptors() {
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 从本地存储获取token
final token = StorageService().getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
));
}
Future<T> request<T>(
String path, {
String method = 'GET',
dynamic data,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.request(
path,
data: data,
queryParameters: queryParameters,
options: Options(method: method),
cancelToken: cancelToken,
);
// 使用 json_serializable 解析数据
return _parseResponse<T>(response.data);
} on DioException catch (e) {
throw ApiException.fromDioException(e);
}
}
}
// 使用 GetX 控制器调用
class UserController extends GetxController {
final ApiService apiService;
var users = <User>[].obs;
var isLoading = false.obs;
UserController(this.apiService);
Future<void> fetchUsers() async {
isLoading.value = true;
try {
final userList = await apiService.request<List<User>>('/users');
users.assignAll(userList);
} on ApiException catch (e) {
Get.snackbar('错误', e.message);
} finally {
isLoading.value = false;
}
}
}
9. 最佳实践与注意事项
-
单例模式:在整个应用中使用单个 Dio 实例,确保配置一致
-
环境区分:为开发、测试、生产环境配置不同的 baseURL
-
安全存储:敏感信息(如 API Keys)不要硬编码在代码中
-
证书验证:生产环境不要忽略 SSL 证书验证
-
内存管理:及时取消不再需要的请求,特别是在页面销毁时
-
错误重试:对特定错误(如网络波动)实现重试机制
-
响应缓存:对不常变的数据实现缓存策略,减少网络请求
-
进度反馈:长时间操作(上传/下载)提供进度提示
10. 扩展资源
这份教程涵盖了 Dio 的核心功能和实际应用场景。建议从基础请求开始,逐步掌握拦截器、错误处理等高级特性,最后根据项目需求进行适当的封装。在实际开发中,合理的封装可以显著提高代码的可维护性和开发效率。