Flutter Dio 网络请求完全指南
Flutter Dio 网络请求完全指南
引言
Dio 是 Flutter 中最流行的网络请求库,它提供了强大的 HTTP 客户端功能。本文将深入探讨 Dio 的各种用法和高级技巧。
基础概念回顾
Dio 特性
- 支持多种请求方法- GET, POST, PUT, DELETE, PATCH, HEAD
- 拦截器- 请求/响应拦截
- 请求取消- 支持取消请求
- 超时配置- 连接超时、接收超时
- FormData- 支持表单数据
- 文件上传/下载- 支持进度监听
基本用法
import 'package:dio/dio.dart'; final dio = Dio(); // GET 请求 Response response = await dio.get('https://api.example.com/data'); // POST 请求 Response response = await dio.post('https://api.example.com/data', data: { 'name': 'John', 'age': 30, });高级技巧一:配置 Dio
基础配置
final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), headers: { 'Authorization': 'Bearer token', 'Content-Type': 'application/json', }, ));自定义配置
dio.options.baseUrl = 'https://api.example.com'; dio.options.connectTimeout = const Duration(seconds: 10); dio.options.receiveTimeout = const Duration(seconds: 5);高级技巧二:拦截器
请求拦截器
dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { options.headers['Authorization'] = 'Bearer token'; return handler.next(options); }, ));响应拦截器
dio.interceptors.add(InterceptorsWrapper( onResponse: (response, handler) { if (response.statusCode == 200) { return handler.next(response); } else { return handler.reject(DioException( requestOptions: response.requestOptions, response: response, )); } }, ));错误拦截器
dio.interceptors.add(InterceptorsWrapper( onError: (DioException e, handler) { if (e.response?.statusCode == 401) { // 处理未授权 } return handler.reject(e); }, ));日志拦截器
dio.interceptors.add(LogInterceptor( requestBody: true, responseBody: true, ));高级技巧三:请求取消
单个请求取消
CancelToken cancelToken = CancelToken(); dio.get( 'https://api.example.com/data', cancelToken: cancelToken, ); // 取消请求 cancelToken.cancel('取消请求');多个请求取消
CancelToken cancelToken = CancelToken(); dio.get('url1', cancelToken: cancelToken); dio.get('url2', cancelToken: cancelToken); dio.get('url3', cancelToken: cancelToken); // 取消所有请求 cancelToken.cancel('全部取消');高级技巧四:文件上传
单个文件上传
FormData formData = FormData.fromMap({ 'file': await MultipartFile.fromFile( '/path/to/file.jpg', filename: 'photo.jpg', ), }); Response response = await dio.post( '/upload', data: formData, );多个文件上传
FormData formData = FormData.fromMap({ 'files': [ await MultipartFile.fromFile('/path/to/file1.jpg'), await MultipartFile.fromFile('/path/to/file2.jpg'), ], }); Response response = await dio.post('/upload', data: formData);上传进度监听
Response response = await dio.post( '/upload', data: formData, onSendProgress: (int sent, int total) { print('上传进度: ${(sent / total * 100).toStringAsFixed(0)}%'); }, );高级技巧五:文件下载
基本下载
await dio.download( 'https://example.com/file.pdf', '/path/to/save/file.pdf', );下载进度监听
await dio.download( 'https://example.com/file.pdf', '/path/to/save/file.pdf', onReceiveProgress: (int received, int total) { print('下载进度: ${(received / total * 100).toStringAsFixed(0)}%'); }, );断点续传
await dio.download( 'https://example.com/file.pdf', '/path/to/save/file.pdf', options: Options( headers: {'Range': 'bytes=1024-'}, ), );高级技巧六:并发请求
并发多个请求
Future<void> fetchData() async { final futures = [ dio.get('/users'), dio.get('/posts'), dio.get('/comments'), ]; final results = await Future.wait(futures); final users = results[0].data; final posts = results[1].data; final comments = results[2].data; }带超时的并发请求
Future<void> fetchWithTimeout() async { try { final result = await Future.any([ dio.get('/data').timeout(const Duration(seconds: 5)), Future.delayed(const Duration(seconds: 3)).then((_) => throw TimeoutException()), ]); } on TimeoutException { // 超时处理 } }实战案例:封装网络请求
创建 ApiService
class ApiService { late Dio _dio; ApiService() { _dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 5), )); _setupInterceptors(); } void _setupInterceptors() { _dio.interceptors.add(LogInterceptor( requestBody: true, responseBody: true, )); _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { final token = _getToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } return handler.next(options); }, onError: (e, handler) { if (e.response?.statusCode == 401) { _handleUnauthorized(); } return handler.reject(e); }, )); } String? _getToken() { // 从存储获取 token return 'user_token'; } void _handleUnauthorized() { // 处理未授权 } Future<User> getUser(int id) async { final response = await _dio.get('/users/$id'); return User.fromJson(response.data); } Future<List<User>> getUsers() async { final response = await _dio.get('/users'); return (response.data as List).map((e) => User.fromJson(e)).toList(); } Future<User> createUser(User user) async { final response = await _dio.post('/users', data: user.toJson()); return User.fromJson(response.data); } Future<void> updateUser(int id, User user) async { await _dio.put('/users/$id', data: user.toJson()); } Future<void> deleteUser(int id) async { await _dio.delete('/users/$id'); } }使用 ApiService
final apiService = ApiService(); // 获取用户 final user = await apiService.getUser(1); // 获取用户列表 final users = await apiService.getUsers(); // 创建用户 final newUser = await apiService.createUser(User(name: 'John')); // 更新用户 await apiService.updateUser(1, User(name: 'John Updated')); // 删除用户 await apiService.deleteUser(1);实战案例:错误处理
自定义异常
enum ApiErrorType { network, unauthorized, notFound, server, unknown, } class ApiException implements Exception { final ApiErrorType type; final String message; final int? statusCode; ApiException({ required this.type, required this.message, this.statusCode, }); }错误处理服务
class ErrorHandler { static ApiException handleError(DioException e) { if (e.type == DioExceptionType.connectionError) { return ApiException( type: ApiErrorType.network, message: '网络连接失败', ); } if (e.type == DioExceptionType.receiveTimeout) { return ApiException( type: ApiErrorType.network, message: '请求超时', ); } final statusCode = e.response?.statusCode; if (statusCode == 401) { return ApiException( type: ApiErrorType.unauthorized, message: '未授权,请重新登录', statusCode: 401, ); } if (statusCode == 404) { return ApiException( type: ApiErrorType.notFound, message: '资源未找到', statusCode: 404, ); } if (statusCode != null && statusCode >= 500) { return ApiException( type: ApiErrorType.server, message: '服务器错误', statusCode: statusCode, ); } return ApiException( type: ApiErrorType.unknown, message: '未知错误', ); } }使用错误处理
try { final user = await apiService.getUser(1); } on DioException catch (e) { final error = ErrorHandler.handleError(e); switch (error.type) { case ApiErrorType.network: // 显示网络错误 break; case ApiErrorType.unauthorized: // 跳转到登录页 break; case ApiErrorType.notFound: // 显示资源未找到 break; case ApiErrorType.server: // 显示服务器错误 break; case ApiErrorType.unknown: // 显示未知错误 break; } }实战案例:缓存策略
缓存拦截器
class CacheInterceptor extends Interceptor { final Map<String, Response> _cache = {}; @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (options.method == 'GET') { final cached = _cache[options.path]; if (cached != null) { return handler.resolve(cached); } } return handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { if (response.requestOptions.method == 'GET') { _cache[response.requestOptions.path] = response; } return handler.next(response); } void clearCache() { _cache.clear(); } void removeCache(String path) { _cache.remove(path); } }使用缓存拦截器
dio.interceptors.add(CacheInterceptor());常见问题与解决方案
Q1:如何处理 SSL 证书问题?
A:配置 HttpClientAdapter:
dio.httpClientAdapter = Http2Adapter( ConnectionManager( idleTimeout: 10000, onClientCreate: (_, config) { config.onBadCertificate = (certificate, host, port) => true; }, ), );Q2:如何设置代理?
A:配置代理:
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.findProxy = (uri) { return 'PROXY proxy.example.com:8888'; }; };Q3:如何处理大文件下载?
A:使用流下载:
final response = await dio.get( 'https://example.com/large_file.zip', options: Options(responseType: ResponseType.stream), ); final file = File('/path/to/save.zip'); final sink = file.openWrite(); await sink.addStream(response.data.stream); await sink.close();最佳实践
1. 封装 Dio 实例
// 推荐:封装到服务类 final apiService = ApiService(); // 不推荐:直接使用 Dio final dio = Dio();2. 使用 Interceptors
// 推荐:使用拦截器统一处理 dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { options.headers['Authorization'] = 'Bearer token'; return handler.next(options); }, ));3. 错误处理
// 推荐:统一错误处理 try { final response = await dio.get('/data'); } on DioException catch (e) { // 处理错误 }总结
Dio 是 Flutter 中功能强大的网络请求库。通过本文的学习,你应该能够:
- 配置 Dio 实例
- 使用拦截器处理请求/响应
- 实现文件上传和下载
- 处理并发请求
- 封装网络请求服务
掌握这些技巧,能够帮助你构建更加健壮的网络层。
