简介
Dio 是一个强大的 Dart Http 请求库,支持拦截器、全局配置、FormData、请求取消、文件下载、超时等特性。
基本配置
安装依赖
dependencies:
dio: ^5.3.2
创建实例
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
基本请求
GET 请求
Future<Response> getUser(String id) async {
try {
final response = await dio.get('/users/$id');
return response;
} catch (e) {
throw e;
}
}
// 带查询参数
Future<Response> searchUsers(String query) async {
try {
final response = await dio.get(
'/users',
queryParameters: {
'search': query,
'page': 1,
'per_page': 20,
},
);
return response;
} catch (e) {
throw e;
}
}
POST 请求
Future<Response> createUser(Map<String, dynamic> data) async {
try {
final response = await dio.post(
'/users',
data: data,
);
return response;
} catch (e) {
throw e;
}
}
高级特性
拦截器
class AuthInterceptor extends Interceptor {
@override
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {
final token = getToken(); // 获取token的方法
options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
}
@override
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) {
// 处理响应数据
handler.next(response);
}
@override
void onError(
DioException err,
ErrorInterceptorHandler handler,
) {
// 处理错误
if (err.response?.statusCode == 401) {
// 处理token过期
refreshToken();
}
handler.next(err);
}
}
// 添加拦截器
dio.interceptors.add(AuthInterceptor());
文件上传
Future<Response> uploadFile(File file) async {
String fileName = file.path.split('/').last;
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
file.path,
filename: fileName,
),
});
try {
final response = await dio.post(
'/upload',
data: formData,
onSendProgress: (sent, total) {
print('${(sent / total * 100).toStringAsFixed(0)}%');
},
);
return response;
} catch (e) {
throw e;
}
}
完整示例
class ApiService {
static final ApiService _instance = ApiService._internal();
factory ApiService() => _instance;
late final Dio _dio;
ApiService._internal() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
_dio.interceptors.add(AuthInterceptor());
_dio.interceptors.add(LogInterceptor(
request: true,
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
error: true,
));
}
// GET 请求
Future<T?> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.get<T>(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return response.data;
} on DioException catch (e) {
_handleError(e);
return null;
}
}
// POST 请求
Future<T?> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return response.data;
} on DioException catch (e) {
_handleError(e);
return null;
}
}
// 错误处理
void _handleError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
throw TimeoutException('连接超时');
case DioExceptionType.sendTimeout:
throw TimeoutException('请求超时');
case DioExceptionType.receiveTimeout:
throw TimeoutException('响应超时');
case DioExceptionType.badResponse:
throw HttpException(
'服务器错误: ${e.response?.statusCode}',
);
case DioExceptionType.cancel:
throw CancellationException('请求被取消');
default:
throw Exception('网络错误: ${e.message}');
}
}
}
// 使用示例
void main() async {
final api = ApiService();
try {
// GET 请求
final user = await api.get<Map<String, dynamic>>(
'/users/1',
);
print('User: $user');
// POST 请求
final newUser = await api.post<Map<String, dynamic>>(
'/users',
data: {
'name': 'John Doe',
'email': '[email protected]',
},
);
print('New user: $newUser');
} catch (e) {
print('Error: $e');
}
}
最佳实践
- 超时给具体值:
connectTimeout: 10秒、receiveTimeout: 15~30秒是常见值;别用默认——Dio 5 的 receiveTimeout 默认无穷大,弱网下请求能 hang 到天荒地老 - 全局单例 + BaseOptions:baseUrl、headers、超时集中配;别每个请求 new 一个 Dio——连接池复用不上,TLS 握手每次重来
- 拦截器做三件事:统一加 token、401 时刷新 token 并重放等待队列(加锁防并发刷新——10 个请求同时 401,不能发 10 次刷新请求)、LogInterceptor 只在 debug 模式开(release 包打日志会泄漏 token)
- CancelToken 跟页面生命周期走:页面 dispose 时
cancelToken.cancel()——否则请求回来时 setState 一个已销毁的页面,直接抛异常
注意事项
- 接口少于十几个、只是简单 GET/POST,直接用 http 包:Dio 的拦截器/取消/FormData 用不上就是 dead dependency(实战案例的教训)
- DioException 按 type 处理:
connectionTimeout提示”网络不给力”,badResponse按 statusCode 分流(401 跳登录、500 提示服务端错误)——别一律 catch 打印”请求失败” - 大文件别走 response.data:上传用 FormData + onSendProgress 显示进度,下载用
dio.download直接写文件;几十 MB 全放内存,低端机直接 OOM - 自签名证书的 badCertificateCallback 只许出现在测试环境:release 包必须删掉,否则中间人攻击形同虚设
实战案例
诚实说:Dio 我配过但没在生产项目深度用过——某物流冷链项目 的 pubspec 里有 dio: ^5.7.0,但 lib/ 里实际没引用(项目初期脚手架带的,后来接口层没接上)。真正发请求的项目是 音乐播放应用,用的是最朴素的 http 包。为什么?音乐 App 的接口就是”搜歌、拿播放链接”两个 GET,http.get 两行搞定,Dio 的拦截器、取消 token、FormData 对我来说是多余重量。网络库选型看接口复杂度:十几个 REST 接口用 http 足够;要统一加 token、重试、日志,才值得上 Dio。别为了”听说 Dio 好”而引入,我那个没用上的 dio 依赖就是教训——dead dependency 也是技术债。
总结
Dio 是一个功能强大的 HTTP 客户端,通过合理使用其提供的特性,可以轻松实现各种网络请求需求。掌握 Dio 的使用对于开发高质量的 Flutter 应用至关重要。
