常用第三方库:dio网络库使用与封装
常用第三方库:dio网络库使用与封装
前言
dio是Flutter生态中最受欢迎的网络请求库之一,它提供了强大的功能和灵活的配置选项。本文将从实战角度深入介绍dio的使用技巧和最佳实践。
基础知识
1. dio简介
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等特性。
2. 基本使用
// 安装依赖
// pubspec.yaml
dependencies:dio: ^5.3.2// 基本使用示例
import 'package:dio/dio.dart';final dio = Dio();// GET请求
Response response = await dio.get('https://api.example.com/users');// POST请求
Response response = await dio.post('https://api.example.com/users',data: {'name': 'test', 'age': 25},
);
3. 配置选项
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com',connectTimeout: Duration(seconds: 5),receiveTimeout: Duration(seconds: 3),headers: {'Authorization': 'Bearer token','Content-Type': 'application/json',},
));
进阶特性
1. 拦截器
拦截器是dio的一大特色,可以在请求发起前和响应返回后进行统一处理。
class CustomInterceptor extends Interceptor {void onRequest(RequestOptions options, RequestInterceptorHandler handler) {// 在请求发起前统一添加tokenoptions.headers['Authorization'] = 'Bearer ${getToken()}';handler.next(options);}void onResponse(Response response, ResponseInterceptorHandler handler) {// 统一处理响应数据if (response.data['code'] == 0) {handler.next(response);} else {handler.reject(DioException(requestOptions: response.requestOptions,response: response,error: response.data['message'],));}}void onError(DioException err, ErrorInterceptorHandler handler) {// 统一错误处理if (err.response?.statusCode == 401) {// 处理token过期refreshToken().then((newToken) {// 重试请求dio.fetch(err.requestOptions);});}handler.next(err);}
}// 添加拦截器
dio.interceptors.add(CustomInterceptor());
2. 请求取消
// 创建取消令牌
final cancelToken = CancelToken();// 发起请求
dio.get('https://api.example.com/data',cancelToken: cancelToken).then((response) {print(response.data);
}).catchError((error) {if (CancelToken.isCancel(error)) {print('请求被取消');}
});// 取消请求
cancelToken.cancel('用户取消');
3. 文件上传下载
// 文件上传
FormData formData = FormData.fromMap({'file': await MultipartFile.fromFile('./text.txt',filename: 'text.txt',),
});Response response = await dio.post('/upload',data: formData,onSendProgress: (int sent, int total) {print('上传进度:${sent / total}');},
);// 文件下载
Response response = await dio.download('https://example.com/file.pdf','./file.pdf',onReceiveProgress: (int received, int total) {print('下载进度:${received / total}');},
);
实战案例:网络请求封装
1. 统一的网络请求类
class HttpClient {static final HttpClient _instance = HttpClient._internal();late Dio dio;factory HttpClient() => _instance;HttpClient._internal() {dio = Dio(BaseOptions(baseUrl: 'https://api.example.com',connectTimeout: Duration(seconds: 5),receiveTimeout: Duration(seconds: 3),));// 添加拦截器dio.interceptors.add(CustomInterceptor());// 添加缓存拦截器dio.interceptors.add(CacheInterceptor());// 添加重试拦截器dio.interceptors.add(RetryInterceptor(dio: dio,retries: 3,retryDelays: const [Duration(seconds: 1),Duration(seconds: 2),Duration(seconds: 3),],),);}Future<T> get<T>(String path, {Map<String, dynamic>? params,Options? options,CancelToken? cancelToken,}) async {try {final response = await dio.get(path,queryParameters: params,options: options,cancelToken: cancelToken,);return _handleResponse<T>(response);} catch (e) {return _handleError(e);}}Future<T> post<T>(String path, {dynamic data,Map<String, dynamic>? params,Options? options,CancelToken? cancelToken,}) async {try {final response = await dio.post(path,data: data,queryParameters: params,options: options,cancelToken: cancelToken,);return _handleResponse<T>(response);} catch (e) {return _handleError(e);}}T _handleResponse<T>(Response response) {if (response.data['code'] == 0) {return response.data['data'];} else {throw ApiException(code: response.data['code'],message: response.data['message'],);}}Never _handleError(dynamic error) {if (error is DioException) {switch (error.type) {case DioExceptionType.connectionTimeout:throw const ApiException(message: '连接超时');case DioExceptionType.sendTimeout:throw const ApiException(message: '请求超时');case DioExceptionType.receiveTimeout:throw const ApiException(message: '响应超时');case DioExceptionType.badResponse:throw ApiException(code: error.response?.statusCode,message: error.response?.statusMessage,);default:throw ApiException(message: error.message);}}throw ApiException(message: error.toString());}
}
2. 缓存拦截器实现
class CacheInterceptor extends Interceptor {final _cache = <String, Response>{};void onRequest(RequestOptions options, RequestInterceptorHandler handler) {// 只缓存GET请求if (options.method == 'GET') {final key = options.uri.toString();final cachedResponse = _cache[key];if (cachedResponse != null) {return handler.resolve(cachedResponse);}}handler.next(options);}void onResponse(Response response, ResponseInterceptorHandler handler) {// 缓存响应数据if (response.requestOptions.method == 'GET') {final key = response.requestOptions.uri.toString();_cache[key] = response;}handler.next(response);}
}
3. 使用示例
// API服务类
class UserService {final _http = HttpClient();Future<User> getUserInfo(String userId) async {final response = await _http.get<Map<String, dynamic>>('/users/$userId',);return User.fromJson(response);}Future<List<User>> getUsers({int page = 1, int size = 20}) async {final response = await _http.get<List<dynamic>>('/users',params: {'page': page, 'size': size},);return response.map((json) => User.fromJson(json)).toList();}Future<void> updateUser(String userId, Map<String, dynamic> data) async {await _http.post<void>('/users/$userId',data: data,);}
}
性能优化建议
-
合理使用拦截器
- 避免在拦截器中进行耗时操作
- 使用异步操作时注意处理异常
-
请求优化
- 合理设置超时时间
- 使用cancelToken取消不必要的请求
- 避免频繁的重复请求
-
缓存策略
- 针对不常变化的数据实现缓存
- 设置合理的缓存过期时间
- 考虑使用本地存储持久化缓存
-
错误处理
- 实现统一的错误处理机制
- 合理使用重试机制
- 提供友好的错误提示
常见问题解决
- 证书验证问题
// 忽略证书验证
dio.options.validateStatus = (status) {return status! < 500;
};// 或者自定义证书验证
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate = (HttpClient client) {client.badCertificateCallback =(X509Certificate cert, String host, int port) => true;return client;};
- 请求取消后的内存泄漏
// 在dispose时取消所有请求
final _cancelTokens = <CancelToken>[];void addCancelToken(CancelToken token) {_cancelTokens.add(token);
}
void dispose() {for (final token in _cancelTokens) {token.cancel();}_cancelTokens.clear();super.dispose();
}
面试题解析
1. dio与http package的区别是什么?
答:dio相比http package有以下优势:
- 更强大的功能:支持拦截器、请求取消、文件上传下载进度监听等
- 更好的扩展性:可以通过拦截器机制实现各种自定义功能
- 更完善的错误处理:提供了统一的错误处理机制
- 更方便的配置:支持全局配置和请求级配置
- 更好的性能:支持请求队列、连接池等优化
2. 如何实现token失效自动刷新?
答:可以通过拦截器实现:
- 在响应拦截器中检测token失效(如401状态码)
- 调用刷新token的接口获取新token
- 使用新token重试原请求
- 注意处理并发请求的情况,避免多次刷新token
class TokenInterceptor extends Interceptor {bool _isRefreshing = false;List<RequestOptions> _pendingRequests = [];void onError(DioException err, ErrorInterceptorHandler handler) async {if (err.response?.statusCode == 401) {final options = err.requestOptions;if (!_isRefreshing) {_isRefreshing = true;try {final newToken = await refreshToken();// 更新所有等待的请求for (final request in _pendingRequests) {request.headers['Authorization'] = 'Bearer $newToken';dio.fetch(request);}_pendingRequests.clear();} finally {_isRefreshing = false;}} else {_pendingRequests.add(options);}return;}handler.next(err);}
}
3. dio如何处理大文件上传?
答:处理大文件上传需要注意以下几点:
- 使用FormData和MultipartFile处理文件上传
- 监听上传进度
- 实现断点续传
- 处理超时和错误重试
Future<void> uploadLargeFile(String filePath) async {final file = File(filePath);final fileSize = await file.length();// 创建FormDatafinal formData = FormData.fromMap({'file': await MultipartFile.fromFile(filePath,filename: basename(filePath),),});try {await dio.post('/upload',data: formData,options: Options(headers: {Headers.contentLengthHeader: fileSize,},sendTimeout: Duration(minutes: 10),),onSendProgress: (int sent, int total) {final progress = sent / total;print('上传进度:${(progress * 100).toStringAsFixed(2)}%');},);} catch (e) {// 处理错误,实现断点续传逻辑}
}
4. 如何优化dio的性能?
答:可以从以下几个方面优化dio的性能:
-
连接池优化
- 合理设置maxConnectionsPerHost
- 复用连接
-
请求优化
- 使用适当的超时设置
- 实现请求合并
- 避免频繁创建dio实例
-
响应优化
- 实现数据缓存
- 使用压缩传输
- 合理处理大量数据
-
内存优化
- 及时释放不需要的资源
- 避免内存泄漏
// 示例:请求合并优化
class RequestMerger {static final _instance = RequestMerger._();final _pending = <String, Completer<dynamic>>{};factory RequestMerger() => _instance;RequestMerger._();Future<T> request<T>(String url, Future<T> Function() fetcher) async {if (_pending.containsKey(url)) {return _pending[url]!.future as Future<T>;}final completer = Completer<T>();_pending[url] = completer;try {final result = await fetcher();completer.complete(result);return result;} catch (e) {completer.completeError(e);rethrow;} finally {_pending.remove(url);}}
}
总结
dio是一个功能强大且灵活的HTTP客户端库,通过合理的封装和优化,可以构建出一个健壮的网络请求层。本文介绍了dio的基本使用、进阶特性、实战案例和性能优化建议,希望能帮助读者更好地使用dio进行网络开发。
参考资源
- dio官方文档:https://pub.dev/packages/dio
- Flutter网络请求最佳实践:https://flutter.dev/docs/development/data-and-backend/networking
- dio源码:https://github.com/flutterchina/dio
如果你对文章内容有任何疑问或建议,欢迎在评论区留言交流。