# Flutter Dio 网络请求库使用教程

0 阅读6分钟

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. 最佳实践与注意事项

  1. 单例模式:在整个应用中使用单个 Dio 实例,确保配置一致
  2. 环境区分:为开发、测试、生产环境配置不同的 baseURL
  3. 安全存储:敏感信息(如 API Keys)不要硬编码在代码中
  4. 证书验证:生产环境不要忽略 SSL 证书验证
  5. 内存管理:及时取消不再需要的请求,特别是在页面销毁时
  6. 错误重试:对特定错误(如网络波动)实现重试机制
  7. 响应缓存:对不常变的数据实现缓存策略,减少网络请求
  8. 进度反馈:长时间操作(上传/下载)提供进度提示

10. 扩展资源

  • 官方文档pub.dev/packages/di…
  • GitHub仓库github.com/cfug/dio
  • Awesome Dio:官方维护的插件和工具列表
  • JSON序列化:配合 json_serializable 处理复杂数据结构
  • 状态管理:与 GetX、Provider、Riverpod 等状态管理库结合使用

这份教程涵盖了 Dio 的核心功能和实际应用场景。建议从基础请求开始,逐步掌握拦截器、错误处理等高级特性,最后根据项目需求进行适当的封装。在实际开发中,合理的封装可以显著提高代码的可维护性和开发效率。