当前位置: 首页 > news >正文

第14讲:HTTP网络请求 - Dio库的使用与封装

导言:

在现代移动应用开发中,与服务器进行HTTP网络通信是必不可少的一环。Flutter提供了基础的 http 包,但功能相对简单。Dio 是一个强大且易用的Dart/Flutter HTTP客户端,它支持拦截器、全局配置、FormData、请求取消、文件上传/下载、超时等高级功能。本讲将带你从零开始,全面掌握Dio的使用,并学会如何对其进行企业级封装。


一、 为什么选择Dio?

在开始之前,我们简单对比一下原生 http 包和 dio

特性Dart http 包Dio
易用性基础,需要手动解析JSONAPI友好,支持多种数据转换
拦截器不支持强大支持,可全局处理请求/响应/错误
文件操作需要自己处理流原生支持文件上传/下载,进度回调
请求取消不支持支持
超时配置全局配置可针对每个请求配置
自动重试需手动实现通过拦截器可轻松实现

结论:对于大多数严肃的商业项目,Dio是不二之选


二、 基础使用:快速上手

1. 添加依赖

在 pubspec.yaml 文件中添加依赖:

yaml

复制

下载

dependencies:dio: ^5.0.0 # 请使用最新版本

然后在终端运行 flutter pub get

2. 发起一个GET请求

dart

复制

下载

import 'package:dio/dio.dart';void fetchUserData() async {try {// 创建一个Dio实例final dio = Dio();// 发起GET请求final response = await dio.get('https://jsonplaceholder.typicode.com/users/1');// 请求成功,response.data 已经是解析好的Map或Listprint(response.data); // 直接是Map类型,无需手动jsonDecodeprint(response.statusCode); // HTTP状态码} on DioException catch (e) {// 使用 DioException 捕获 Dio 相关的错误// 这是 Dio 5.x 的变更,之前是 DioErrorprint('请求出错: ${e.message}');if (e.response != null) {// 服务器返回了错误状态码(如404, 500等)print('服务器错误: ${e.response?.statusCode}');print('服务器错误信息: ${e.response?.data}');}} catch (e) {// 处理其他异常(如网络连接失败)print('其他错误: $e');}
}

3. 发起一个POST请求

dart

复制

下载

void postUserData() async {final dio = Dio();try {final response = await dio.post('https://jsonplaceholder.typicode.com/posts',data: {'title': 'foo','body': 'bar','userId': 1,}, // Dio会自动将Map编码为JSONoptions: Options(headers: {'Content-Type': 'application/json; charset=UTF-8',},),);print('创建成功: ${response.data}');} on DioException catch (e) {print('提交失败: ${e.message}');}
}

三、 核心配置与全局初始化

在实际项目中,我们通常会创建一个全局的、配置好的Dio实例。

dart

复制

下载

// network/dio_client.dartclass DioClient {// 静态实例static final DioClient _instance = DioClient._internal();// 工厂构造函数,返回单例factory DioClient() {return _instance;}// 内部的Dio实例final Dio dio;// 私有构造函数,在这里进行初始化DioClient._internal(): dio = Dio(BaseOptions(// 基础URL,后续所有请求会自动拼接baseUrl: 'https://jsonplaceholder.typicode.com',// 连接超时时间(毫秒)connectTimeout: const Duration(seconds: 10),// 接收超时时间(毫秒)receiveTimeout: const Duration(seconds: 10),// 发送超时时间(毫秒)sendTimeout: const Duration(seconds: 10),// 请求头headers: {'Content-Type': 'application/json',},),) {// 可以在这里添加拦截器_addInterceptors();}void _addInterceptors() {// 添加日志拦截器(需要 dio_logger 包)// dio.interceptors.add(LogInterceptor());// 添加自定义拦截器dio.interceptors.add(CustomInterceptor());}
}

四、 拦截器(Interceptors):Dio的灵魂

拦截器允许你在请求发出前或响应返回后,对其进行统一处理。

1. 自定义拦截器示例

dart

复制

下载

// interceptors/custom_interceptor.dartclass CustomInterceptor extends Interceptor {@overridevoid onRequest(RequestOptions options, RequestInterceptorHandler handler) {print('🚀 发出请求: ${options.method} ${options.uri}');// 在实际项目中,可以在这里统一添加Token// options.headers['Authorization'] = 'Bearer $token';// 必须调用 handler.next 继续执行handler.next(options);}@overridevoid onResponse(Response response, ResponseInterceptorHandler handler) {print('✅ 收到响应: ${response.statusCode} ${response.requestOptions.uri}');handler.next(response);}@overridevoid onError(DioException err, ErrorInterceptorHandler handler) {print('❌ 请求错误: ${err.type} - ${err.message}');// 统一错误处理_handleError(err);handler.next(err);}void _handleError(DioException err) {switch (err.type) {case DioExceptionType.connectionTimeout:case DioExceptionType.sendTimeout:case DioExceptionType.receiveTimeout:// 显示超时错误提示break;case DioExceptionType.badResponse:// 处理服务器返回的错误状态码final statusCode = err.response?.statusCode;if (statusCode == 401) {// Token过期,跳转到登录页// _goToLogin();}break;case DioExceptionType.cancel:// 请求被取消break;case DioExceptionType.unknown:// 其他错误,通常是网络问题break;default:break;}}
}

2. 使用拦截器实现自动Token刷新

这是一个高级但非常实用的场景:

dart

复制

下载

class TokenRefreshInterceptor extends Interceptor {final Dio _dio;final Future<String> Function() _refreshToken;TokenRefreshInterceptor(this._dio, this._refreshToken);@overridevoid onError(DioException err, ErrorInterceptorHandler handler) async {// 如果是401错误且不是刷新Token的请求本身if (err.response?.statusCode == 401 && !err.requestOptions.path.contains('/refresh-token')) {try {// 尝试刷新Tokenfinal newToken = await _refreshToken();// 更新请求头中的Tokenerr.requestOptions.headers['Authorization'] = 'Bearer $newToken';// 重新发起失败的请求final response = await _dio.fetch(err.requestOptions);handler.resolve(response); // 返回成功的结果} catch (e) {// 刷新Token失败,跳转到登录页// _goToLogin();handler.reject(err); // 继续抛出错误}} else {handler.next(err);}}
}

五、 企业级封装:ApiService模式

为了更好的维护性和可测试性,我们通常会对网络请求进行进一步封装。

1. 定义API端点

dart

复制

下载

// api/endpoints.dartabstract class ApiEndpoints {static const String baseUrl = 'https://jsonplaceholder.typicode.com';static const String getUser = '/users/{id}';static const String createPost = '/posts';static const String uploadImage = '/upload';
}

2. 创建ApiService

dart

复制

下载

// services/api_service.dartclass ApiService {final Dio _dio;ApiService(this._dio);// 获取用户信息Future<User> getUser(int id) async {final response = await _dio.get(ApiEndpoints.getUser.replaceFirst('{id}', id.toString()),);return User.fromJson(response.data);}// 创建帖子Future<Post> createPost(Post post) async {final response = await _dio.post(ApiEndpoints.createPost,data: post.toJson(),);return Post.fromJson(response.data);}// 文件上传Future<String> uploadImage(String filePath) async {// 创建FormDatafinal formData = FormData.fromMap({'file': await MultipartFile.fromFile(filePath),});final response = await _dio.post(ApiEndpoints.uploadImage,data: formData,// 上传进度回调onSendProgress: (sent, total) {if (total != -1) {print('上传进度: ${(sent / total * 100).toStringAsFixed(0)}%');}},);return response.data['url']; // 假设返回图片URL}
}

3. 在项目中使用的完整示例

dart

复制

下载

// main.dart 或依赖注入的设置void main() {// 初始化Dio客户端final dioClient = DioClient();// 创建ApiServicefinal apiService = ApiService(dioClient.dio);runApp(MyApp(apiService: apiService));
}class MyApp extends StatelessWidget {final ApiService apiService;const MyApp({super.key, required this.apiService});@overrideWidget build(BuildContext context) {return MaterialApp(home: UserProfilePage(apiService: apiService),);}
}// 使用ApiService的页面
class UserProfilePage extends StatefulWidget {final ApiService apiService;const UserProfilePage({super.key, required this.apiService});@overrideState<UserProfilePage> createState() => _UserProfilePageState();
}class _UserProfilePageState extends State<UserProfilePage> {User? _user;bool _isLoading = false;@overridevoid initState() {super.initState();_fetchUserData();}Future<void> _fetchUserData() async {setState(() {_isLoading = true;});try {final user = await widget.apiService.getUser(1);setState(() {_user = user;});} on DioException catch (e) {// 显示错误提示ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('加载失败: ${e.message}')),);} finally {setState(() {_isLoading = false;});}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('用户信息')),body: _isLoading? const Center(child: CircularProgressIndicator()): _user != null? UserInfoWidget(user: _user!): const Center(child: Text('加载失败')),);}
}

六、 错误处理的统一规范

为了在整个应用中保持一致的错误处理体验,建议创建统一的错误处理工具:

dart

复制

下载

// utils/error_handler.dartclass ErrorHandler {static String getErrorMessage(DioException e) {switch (e.type) {case DioExceptionType.connectionTimeout:case DioExceptionType.sendTimeout:case DioExceptionType.receiveTimeout:return '网络连接超时,请检查网络后重试';case DioExceptionType.badResponse:switch (e.response?.statusCode) {case 401:return '登录已过期,请重新登录';case 404:return '请求的资源不存在';case 500:case 502:case 503:return '服务器开小差了,请稍后重试';default:return '网络请求失败: ${e.response?.statusCode}';}case DioExceptionType.cancel:return '请求已取消';case DioExceptionType.unknown:return '网络连接失败,请检查网络设置';default:return '未知错误,请稍后重试';}}
}

七、 总结与最佳实践

通过本讲的学习,你应该已经掌握了:

  1. Dio的基础使用:GET/POST请求,错误处理。

  2. 全局配置:创建配置良好的单例Dio客户端。

  3. 拦截器:日志记录、统一错误处理、Token自动刷新。

  4. 企业级封装:ApiService模式,职责分离。

  5. 文件上传:使用FormData进行文件操作。

http://www.dtcms.com/a/573511.html

相关文章:

  • 西安市城乡建设管理局网站唐山专业网站建设公司
  • Flink集群部署以及作业提交模式详解
  • Windows系统Git的安装及在IDEA中的配置
  • Linux网络(二)——socket编程
  • 图书出版的幕后故事-《JMeter核心技术、性能测试与性能分析》背后不为人知的事
  • 最好的做网站公司有哪些河北网站推广优化
  • Voronoi 图及其在路径搜索中的应用
  • 网站模版自适应建设商务网站ppt
  • 舞台灯光透镜厂数字化:AI赋能光学检测与镀膜调控新范式
  • 买国外空间哪个网站好中国正式宣布出兵
  • 建设网站需要注册证书吗建站排行榜
  • AWS区域显示工具:统一化设计与实现
  • Valgrind 在嵌入式 Linux 平台:工作原理、典型场景与案例分析
  • 仙桃网站设计游戏优化是什么意思
  • journalctl 日志清理
  • 【javaFX基础】示例“无标题“控制器类的骨架、public class PleaseProvideControllerClassName {}问题处理
  • 计算文章的相似度
  • 网络通信的奥秘:HTTP详解 (六)
  • 郴州网站建设设计网站开发工程师职位概要
  • 夸克网盘下载速度几十KB怎么解决?- 在线免费工具
  • 如何搭建自己的交易系统
  • 分苹果问题
  • 2025年人工智能领域五大认证体系全景解析与选择策略
  • 人工智能导论(期末复习)
  • 怎么用软件做原创视频网站dw个人网站设计模板
  • Linux 安装 Elasticsearch:避坑指南 + 性能调优实战
  • 测开学习DAY23
  • 站长之家域名信息查询微信店铺怎么开
  • 复盘Netflix的2025:广告业务、线下业态和视频播客
  • AI生成音频:技术概述与实践指南