Flutter网络层封装:Dio最佳实践与架构设计 1. 为什么Flutter项目需要网络层封装在Flutter开发中直接使用Dio进行网络请求就像在建筑工地直接使用钢筋水泥砌墙——虽然材料本身质量很好但缺乏系统性的架构设计会让整个项目变得难以维护。让我们先看一个典型场景假设你的应用有20个API调用如果直接使用Dio你会发现Token处理、错误处理、日志打印等代码会重复出现在每个请求中。1.1 直接使用Dio的六大痛点代码冗余每个请求都需要重复编写基础配置、错误处理逻辑维护困难当需要修改请求头或基础URL时需要逐个修改每个请求异常处理不统一不同开发者可能采用不同的错误处理方式缺乏扩展性新增功能如缓存、重试机制需要修改大量现有代码调试困难没有统一的日志输出格式安全性风险敏感信息可能散落在代码各处// 典型的直接使用Dio的代码 - 问题显而易见 Futurevoid fetchData() async { try { final dio Dio(); final response await dio.get(https://api.example.com/data); // 处理响应... } catch (e) { // 每个catch块都要写相同的错误处理 print(Error: $e); } }1.2 网络层封装的四大优势统一管理所有网络相关配置集中在一处代码复用通用逻辑如Token添加、错误处理只需实现一次易于维护修改网络行为只需调整封装层功能扩展可以方便地添加拦截器、缓存等高级功能2. 网络层封装的核心设计2.1 三层架构设计一个健壮的网络层通常采用三层架构基础设施层处理原始HTTP请求、响应服务层实现业务逻辑相关的网络功能接口层对外提供简洁的API调用方式// 基础设施层示例 abstract class HttpClient { FutureResponse get(String url, {MapString, dynamic? queryParams}); FutureResponse post(String url, {dynamic body}); } // 服务层示例 class AuthService { final HttpClient client; FutureUser login(String email, String password) async { final response await client.post(/login, body: { email: email, password: password }); return User.fromJson(response.data); } }2.2 关键组件实现2.2.1 基础封装class NetworkService { final Dio _dio Dio(); NetworkService() { _dio.options BaseOptions( baseUrl: https://api.example.com, connectTimeout: Duration(seconds: 5), receiveTimeout: Duration(seconds: 10), ); _addInterceptors(); } void _addInterceptors() { _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { // 统一添加Token options.headers[Authorization] Bearer token; return handler.next(options); }, onError: (error, handler) { // 统一错误处理 return handler.next(error); }, )); } FutureResponse get(String path, {MapString, dynamic? query}) async { try { return await _dio.get(path, queryParameters: query); } on DioException catch (e) { throw _handleError(e); } } // 其他HTTP方法... }2.2.2 错误处理标准化class NetworkError { final String message; final int? statusCode; NetworkError(this.message, this.statusCode); override String toString() NetworkError: $message (status: $statusCode); } NetworkError _handleError(DioException error) { if (error.response ! null) { return NetworkError( error.response?.data[message] ?? Unknown error, error.response?.statusCode, ); } else { return NetworkError(error.message ?? Network error, null); } }3. 高级功能实现3.1 Token自动刷新机制Token过期是移动端常见问题合理的刷新机制可以提升用户体验bool _isRefreshing false; ListCompleter _pendingRequests []; Futurevoid _handleTokenExpired(DioException error, Dio dio) async { if (error.response?.statusCode 401 !_isRefreshing) { _isRefreshing true; try { final newToken await _refreshToken(); await _saveToken(newToken); // 重试所有挂起的请求 for (var completer in _pendingRequests) { completer.complete(); } } catch (e) { // 刷新失败跳转到登录页 _navigateToLogin(); } finally { _isRefreshing false; _pendingRequests.clear(); } } } FutureString _refreshToken() async { final refreshToken await _getRefreshToken(); final response await _dio.post(/refresh, data: { refresh_token: refreshToken }); return response.data[access_token]; }3.2 请求缓存策略合理的缓存可以显著提升应用响应速度class CacheInterceptor extends Interceptor { final MapString, CacheItem _cache {}; override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (options.method GET) { final cacheKey _getCacheKey(options); if (_cache.containsKey(cacheKey) !_cache[cacheKey]!.isExpired()) { return handler.resolve( Response( requestOptions: options, data: _cache[cacheKey]!.data, ), ); } } handler.next(options); } override void onResponse(Response response, ResponseInterceptorHandler handler) { if (response.requestOptions.method GET) { final cacheKey _getCacheKey(response.requestOptions); _cache[cacheKey] CacheItem( data: response.data, ttl: Duration(minutes: 5), ); } handler.next(response); } String _getCacheKey(RequestOptions options) { return ${options.path}?${options.queryParameters}; } } class CacheItem { final dynamic data; final DateTime createdAt; final Duration ttl; CacheItem({ required this.data, Duration? ttl, }) : createdAt DateTime.now(), ttl ttl ?? Duration(minutes: 5); bool isExpired() { return DateTime.now().difference(createdAt) ttl; } }4. 实战中的经验与陷阱4.1 必须避免的五个常见错误全局单例滥用虽然Dio实例应该复用但不恰当的全局状态会导致测试困难忽略取消机制页面销毁时不取消请求会导致内存泄漏过度封装封装应该保持适度避免创建过于复杂的抽象层硬编码配置baseURL等配置应该支持环境区分忽视日志记录完善的日志是调试网络问题的关键4.2 性能优化技巧连接池配置调整Dio的连接池大小提升性能_dio.httpClientAdapter IOHttpClientAdapter() ..createHttpClient () { final client HttpClient(); client.connectionTimeout _dio.options.connectTimeout; client.maxConnectionsPerHost 5; // 重要优化点 return client; };压缩响应启用gzip压缩减少数据传输量_dio.options.headers[Accept-Encoding] gzip;合理设置超时根据API特性设置不同的超时时间// 普通API请求 const Duration normalTimeout Duration(seconds: 10); // 文件上传/下载 const Duration fileTransferTimeout Duration(minutes: 5);4.3 测试策略完善的网络层应该易于测试test(should handle 401 error by refreshing token, () async { final mockDio MockDio(); when(mockDio.get(any)).thenThrow( DioException( requestOptions: RequestOptions(path: /protected), response: Response( requestOptions: RequestOptions(path: /protected), statusCode: 401, ), ), ); when(mockDio.post(/refresh)).thenAnswer( (_) async Response( requestOptions: RequestOptions(path: /refresh), data: {access_token: new_token}, ), ); final service AuthService(mockDio); await expectLater( service.getProtectedData(), throwsA(isANetworkError()), ); verify(mockDio.post(/refresh)).called(1); });5. 完整实现方案下面是一个生产环境可用的网络层完整实现import package:dio/dio.dart; import package:flutter/foundation.dart; class ApiClient { final Dio _dio; final String baseUrl; final _pendingRequests String, Completer{}; bool _isRefreshingToken false; ApiClient({ required this.baseUrl, Interceptor? logger, }) : _dio Dio() { _dio.options BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 15), headers: { Accept: application/json, Content-Type: application/json, }, ); _dio.interceptors.add(InterceptorsWrapper( onRequest: _addAuthToken, onError: _handleAuthError, )); if (logger ! null) { _dio.interceptors.add(logger); } if (kDebugMode) { _dio.interceptors.add(LogInterceptor( requestBody: true, responseBody: true, )); } } Futurevoid _addAuthToken( RequestOptions options, RequestInterceptorHandler handler, ) async { final token await _getToken(); if (token ! null) { options.headers[Authorization] Bearer $token; } handler.next(options); } Futurevoid _handleAuthError( DioException err, ErrorInterceptorHandler handler, ) async { if (err.response?.statusCode 401) { final requestKey err.requestOptions.uri.toString(); if (!_isRefreshingToken) { _isRefreshingToken true; try { final newToken await _refreshToken(); await _saveToken(newToken); // 重试所有挂起的请求 for (var completer in _pendingRequests.values) { completer.complete(); } } catch (e) { // 刷新失败清除所有挂起请求 for (var completer in _pendingRequests.values) { completer.completeError(e); } } finally { _pendingRequests.clear(); _isRefreshingToken false; } // 重试原始请求 try { final response await _retryRequest(err.requestOptions); return handler.resolve(response); } catch (e) { return handler.reject(e as DioException); } } else { // 如果已经在刷新Token将请求加入等待队列 final completer Completer(); _pendingRequests[requestKey] completer; return completer.future.then((_) { return _retryRequest(err.requestOptions).then( (r) handler.resolve(r), onError: (e) handler.reject(e as DioException), ); }); } } handler.next(err); } FutureResponse _retryRequest(RequestOptions options) async { return _dio.request( options.path, data: options.data, queryParameters: options.queryParameters, options: Options( method: options.method, headers: options.headers, ), ); } // 以下方法需要根据实际项目实现 FutureString? _getToken() async null; FutureString _refreshToken() async ; Futurevoid _saveToken(String token) async {} // 公共API方法 FutureResponse get(String path, {MapString, dynamic? params}) { return _dio.get(path, queryParameters: params); } FutureResponse post(String path, {dynamic data}) { return _dio.post(path, data: data); } // 其他HTTP方法... }这个实现包含了Token自动刷新、请求重试、日志记录等核心功能可以直接用于生产环境项目。根据项目需求你可以进一步扩展缓存、请求队列等功能。