news 2026/5/12 18:53:03

Flutter 网络请求完全指南:Dio 封装与拦截器实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 网络请求完全指南:Dio 封装与拦截器实战

Flutter 网络请求完全指南:Dio 封装与拦截器实战

在 Flutter 开发中,网络请求是连接前端与后端服务的核心桥梁,直接影响应用的交互体验与数据流转效率。Dio 作为 Flutter 生态中最主流的网络请求库,支持 RESTful API、FormData、拦截器、请求取消、超时设置等丰富功能,几乎能满足所有网络请求场景需求。本文将从 Dio 基础用法入手,逐步深入到企业级封装方案、拦截器实战技巧,再到异常处理、请求取消等高级用法,为开发者提供一份全面的 Dio 实战指南。

作者:爱吃大芒果

个人主页 爱吃大芒果

本文所属专栏 Flutter

更多专栏

Ascend C 算子开发教程(进阶)
鸿蒙集成
从0到1自学C++


一、Dio 基础:快速上手与核心配置

在进行复杂封装前,首先需要掌握 Dio 的基础用法与核心配置,快速实现简单的 GET/POST 请求。

1. 环境准备:添加依赖与权限

首先在pubspec.yaml中添加 Dio 依赖(推荐使用最新稳定版,可在 pub.dev 查看最新版本):

dependencies:flutter:sdk:flutterdio:^5.4.3# 最新稳定版json_annotation:^4.8.1# 可选,用于 JSON 序列化(推荐配套使用)flutter_dotenv:^5.1.0# 可选,用于管理环境变量(如 baseUrl)

针对 Android 平台,需在android/app/src/main/AndroidManifest.xml中添加网络权限:

<uses-permissionandroid:name="android.permission.INTERNET"/>

针对 iOS 平台,需在ios/Runner/Info.plist中添加以下配置(iOS 10+ 要求):

<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>

注意:生产环境中不建议直接开启NSAllowsArbitraryLoads,应针对性配置NSExceptionDomains允许指定域名的 HTTP 访问,或直接使用 HTTPS。

2. 核心配置:Dio 实例初始化

Dio 支持通过构造函数或options属性配置全局参数,核心配置项包括:

  • baseUrl:基础请求地址(避免重复拼接 URL);

  • connectTimeout:连接超时时间(默认 5 秒);

  • receiveTimeout:接收超时时间(默认 30 秒);

  • headers:全局请求头(如 Token、Content-Type);

  • responseType:响应数据类型(默认ResponseType.json)。

基础初始化示例:

import'package:dio/dio.dart';// 初始化 Dio 实例finalDio dio=Dio()..options=BaseOptions(baseUrl:"https://api.example.com/v1",// 基础地址connectTimeout:constDuration(seconds:5),// 连接超时receiveTimeout:constDuration(seconds:30),// 接收超时headers:{"Content-Type":"application/json","User-Agent":"Flutter-Dio-Client",},responseType:ResponseType.json,);

3. 基础请求:GET 与 POST 实现

Dio 对 GET/POST 等常用请求提供了简洁的 API,支持异步调用(使用async/await)。

(1)GET 请求:查询参数传递

GET 请求通过queryParameters参数传递查询参数,适用于数据查询场景:

// 发起 GET 请求(获取用户列表)Future<List<User>>getUserList({int page=1,int size=20})async{try{finalresponse=awaitdio.get("/users",// 接口路径(拼接 baseUrl 后为 https://api.example.com/v1/users)queryParameters:{"page":page,"size":size},// 查询参数);// 解析响应数据(假设后端返回格式为 { "code": 200, "data": [...], "msg": "success" })if(response.data["code"]==200){return(response.data["data"]asList).map((json)=>User.fromJson(json)).toList();}else{throwException("获取用户列表失败:${response.data["msg"]}");}}catch(e){throwException("请求异常:$e");}}
(2)POST 请求:JSON 与 FormData 提交

POST 请求适用于数据提交场景,支持 JSON 格式与 FormData 格式(文件上传常用):

// 1. JSON 格式提交(用户登录)Future<LoginResponse>login({required String username,required String password})async{try{finalresponse=awaitdio.post("/login",data:{"username":username,"password":password},// JSON 数据);if(response.data["code"]==200){returnLoginResponse.fromJson(response.data["data"]);}else{throwException("登录失败:${response.data["msg"]}");}}catch(e){throwException("登录异常:$e");}}// 2. FormData 格式提交(文件上传)Future<String>uploadFile({required String filePath})async{try{finalformData=FormData.fromMap({"file":awaitMultipartFile.fromFile(filePath,filename:filePath.split("/").last,// 文件名),"type":"avatar",// 额外参数});finalresponse=awaitdio.post("/upload",data:formData,options:Options(headers:{"Content-Type":"multipart/form-data"},// 自动设置,可省略),);if(response.data["code"]==200){returnresponse.data["data"]["fileUrl"];// 返回文件 URL}else{throwException("文件上传失败:${response.data["msg"]}");}}catch(e){throwException("上传异常:$e");}}

二、企业级封装:高内聚低耦合的 Dio 工具类

基础用法虽简洁,但在大型项目中存在代码冗余、维护困难等问题。通过封装 Dio 工具类,可实现请求统一管理、全局配置复用、异常集中处理,提升代码可维护性。

1. 封装思路:分层设计与单一职责

采用“工具类 + 接口层 + 模型层”的分层设计:

  • 工具类(DioUtil):封装 Dio 实例、全局配置、拦截器、请求方法(get/post/upload 等);

  • 接口层(ApiService):集中管理所有接口地址与请求参数,避免硬编码;

  • 模型层(Model):通过 JSON 序列化工具生成数据模型,统一解析响应数据。

2. 完整封装实现:DioUtil 工具类

import'package:dio/dio.dart';import'package:flutter_dotenv/flutter_dotenv.dart';// 网络请求工具类classDioUtil{// 单例模式(懒加载)staticfinalDioUtil _instance=DioUtil._internal();factoryDioUtil()=>_instance;late Dio _dio;// 私有构造函数:初始化 Dio 配置DioUtil._internal(){_dio=Dio()..options=BaseOptions(baseUrl:dotenv.env["BASE_URL"]??"https://api.example.com/v1",// 从环境变量读取 baseUrlconnectTimeout:constDuration(seconds:5),receiveTimeout:constDuration(seconds:30),headers:{"Content-Type":"application/json"},);// 添加拦截器(后续详解)_addInterceptors();}// 添加拦截器void_addInterceptors(){// 请求拦截器:添加 Token、日志打印等_dio.interceptors.add(InterceptorsWrapper(onRequest:(options,handler){// 示例:添加 Token(从本地缓存获取)finalString?token=_getLocalToken();if(token!=null){options.headers["Authorization"]="Bearer $token";}print("请求信息:${options.method} ${options.uri},参数:${options.data}");returnhandler.next(options);// 继续请求},onResponse:(response,handler){print("响应信息:${response.statusCode},数据:${response.data}");returnhandler.next(response);// 继续处理响应},onError:(DioException e,handler){print("请求异常:${e.message}");returnhandler.next(e);// 继续处理异常},),);// 日志拦截器(可选,用于调试)_dio.interceptors.add(LogInterceptor(responseBody:true));}// 从本地缓存获取 Token(示例方法,实际需结合本地存储库如 shared_preferences)String?_getLocalToken(){// 实际场景:return await SharedPreferences.getInstance().then((prefs) => prefs.getString("token"));return"test_token_123456";}// 通用 GET 请求Future<T>get<T>(String path,{Map<String,dynamic>?queryParameters,Options?options,})async{try{finalresponse=await_dio.get(path,queryParameters:queryParameters,options:options,);return_handleResponse<T>(response);}onDioExceptioncatch(e){_handleDioError(e);rethrow;// 抛出异常,让业务层处理}}// 通用 POST 请求Future<T>post<T>(String path,{dynamicdata,Map<String,dynamic>?queryParameters,Options?options,})async{try{finalresponse=await_dio.post(path,data:data,queryParameters:queryParameters,options:options,);return_handleResponse<T>(response);}onDioExceptioncatch(e){_handleDioError(e);rethrow;}}// 通用文件上传请求Future<T>upload<T>(String path,{required FormData formData,Options?options,})async{try{finalresponse=await_dio.post(path,data:formData,options:options??Options(headers:{"Content-Type":"multipart/form-data"},),);return_handleResponse<T>(response);}onDioExceptioncatch(e){_handleDioError(e);rethrow;}}// 响应处理:统一解析后端返回格式T _handleResponse<T>(Response response){finalMap<String,dynamic>data=response.data;// 假设后端统一返回格式:{ "code": int, "data": T, "msg": String }if(data["code"]==200){returndata["data"]asT;}else{throwException("业务异常:${data["msg"] ?? "未知错误"}");}}// Dio 异常处理:分类处理超时、网络错误、404/500 等void_handleDioError(DioException e){switch(e.type){caseDioExceptionType.connectionTimeout:throwException("连接超时,请检查网络");caseDioExceptionType.receiveTimeout:throwException("接收超时,服务器响应缓慢");caseDioExceptionType.connectionError:throwException("网络错误,请检查网络连接");caseDioExceptionType.notFound:throwException("接口不存在(404)");caseDioExceptionType.badResponse:throwException("服务器错误(${e.response?.statusCode})");default:throwException("请求失败:${e.message}");}}// 取消请求(需配合 CancelToken 使用)voidcancelRequest(CancelToken cancelToken){cancelToken.cancel("请求已取消");}}

3. 接口层与模型层配合使用

(1)接口层(ApiService):集中管理接口
import'package:json_annotation/json_annotation.dart';import'dio_util.dart';// 接口地址常量classApiPath{staticconstString login="/login";staticconstString getUserList="/users";staticconstString uploadFile="/upload";}// 接口服务类classApiService{staticfinalDioUtil _dio=DioUtil();// 登录接口staticFuture<LoginResponse>login({required String username,required String password})async{finaldata=await_dio.post<Map<String,dynamic>>(ApiPath.login,data:{"username":username,"password":password},);returnLoginResponse.fromJson(data);}// 获取用户列表接口staticFuture<List<User>>getUserList({int page=1,int size=20})async{finaldata=await_dio.get<List<dynamic>>(ApiPath.getUserList,queryParameters:{"page":page,"size":size},);returndata.map((json)=>User.fromJson(json)).toList();}// 文件上传接口staticFuture<UploadResponse>uploadAvatar({required String filePath})async{finalformData=FormData.fromMap({"file":awaitMultipartFile.fromFile(filePath,filename:filePath.split("/").last),"type":"avatar",});finaldata=await_dio.upload<Map<String,dynamic>>(ApiPath.uploadFile,formData:formData,);returnUploadResponse.fromJson(data);}}
(2)模型层(通过 json_serializable 生成)

首先创建模型类(如login_response.dart),并通过注解定义序列化规则:

import'package:json_annotation/json_annotation.dart';part'login_response.g.dart';// 生成的代码文件@JsonSerializable()classLoginResponse{finalString token;finalString username;finalString avatar;LoginResponse({requiredthis.token,requiredthis.username,requiredthis.avatar,});// 从 JSON 解析模型factoryLoginResponse.fromJson(Map<String,dynamic>json)=>_$LoginResponseFromJson(json);// 模型转换为 JSONMap<String,dynamic>toJson()=>_$LoginResponseToJson(this);}

执行以下命令生成序列化代码:

flutter pub run build_runner build

三、拦截器实战:请求/响应增强与异常拦截

Dio 拦截器是其核心特性之一,支持在请求发起前、响应返回后、异常发生时插入自定义逻辑,实现 Token 自动添加、日志打印、刷新 Token、缓存处理等功能。

1. 拦截器核心原理

Dio 拦截器基于“责任链模式”设计,支持添加多个拦截器,按添加顺序执行:

  • 请求拦截器(onRequest):在请求发起前执行,可修改请求参数(如添加 Token、动态修改 baseUrl);

  • 响应拦截器(onResponse):在响应返回后执行,可统一解析响应数据、处理缓存;

  • 异常拦截器(onError):在请求异常时执行,可统一处理错误(如 Token 过期刷新、网络错误提示)。

2. 实战场景 1:Token 自动添加与过期刷新

在请求拦截器中自动添加 Token,在异常拦截器中处理 Token 过期(401 错误)并刷新 Token 后重试请求:

void_addInterceptors(){_dio.interceptors.add(InterceptorsWrapper(onRequest:(options,handler)async{// 1. 添加 TokenfinalString?token=await_getLocalToken();if(token!=null){options.headers["Authorization"]="Bearer $token";}handler.next(options);},onError:(DioException e,handler)async{// 2. 处理 Token 过期(401 错误)if(e.response?.statusCode==401){// 2.1 锁定拦截器,避免并发请求重复刷新 Token_dio.lock();try{// 2.2 调用刷新 Token 接口finalString newToken=await_refreshToken();if(newToken.isNotEmpty){// 2.3 保存新 Token 到本地await_saveLocalToken(newToken);// 2.4 重新设置请求头中的 Tokene.requestOptions.headers["Authorization"]="Bearer $newToken";// 2.5 重试原请求finalResponse response=await_dio.fetch(e.requestOptions);returnhandler.resolve(response);// 重试成功,返回新响应}}catch(refreshError){// 2.6 刷新 Token 失败,跳转登录页_navigateToLogin();}finally{// 2.7 解锁拦截器_dio.unlock();}}handler.next(e);// 继续处理其他错误},),);}// 刷新 Token 接口(实际需对接后端)Future<String>_refreshToken()async{finalresponse=await_dio.post("/refreshToken",data:{"refreshToken":await_getLocalRefreshToken()},);if(response.data["code"]==200){returnresponse.data["data"]["token"];}else{throwException("刷新 Token 失败");}}

3. 实战场景 2:请求缓存拦截器

通过拦截器实现 GET 请求缓存,减少重复网络请求,提升离线体验(需配合本地存储库如hiveshared_preferences):

import'package:hive/hive.dart';// 缓存拦截器classCacheInterceptorextendsInterceptor{finalBox _cacheBox=Hive.box("network_cache");// 初始化 Hive 缓存箱@overridevoidonRequest(RequestOptions options,RequestHandler handler){// 仅对 GET 请求启用缓存if(options.method=="GET"){finalString cacheKey=_generateCacheKey(options);finaldynamiccacheData=_cacheBox.get(cacheKey);// 缓存存在且未过期,直接返回缓存数据if(cacheData!=null&&!_isCacheExpired(cacheData["timestamp"])){returnhandler.resolve(Response(requestOptions:options,statusCode:200,data:cacheData["data"],),);}}handler.next(options);}@overridevoidonResponse(Response response,ResponseHandler handler){// 对 GET 请求结果进行缓存if(response.requestOptions.method=="GET"){finalString cacheKey=_generateCacheKey(response.requestOptions);_cacheBox.put(cacheKey,{"data":response.data,"timestamp":DateTime.now().millisecondsSinceEpoch,// 缓存时间戳},);}handler.next(response);}// 生成缓存 Key(基于 URL + 查询参数)String_generateCacheKey(RequestOptions options){return"${options.uri.toString()}?${options.queryParameters.toString()}";}// 判断缓存是否过期(假设缓存有效期为 5 分钟)bool_isCacheExpired(int timestamp){constint cacheDuration=5*60*1000;// 5 分钟(毫秒)returnDateTime.now().millisecondsSinceEpoch-timestamp>cacheDuration;}}

添加缓存拦截器到 Dio 实例:

_dio.interceptors.add(CacheInterceptor());

4. 实战场景 3:日志拦截器与调试优化

Dio 内置LogInterceptor,可快速打印请求/响应日志,便于调试:

_dio.interceptors.add(LogInterceptor(request:true,// 打印请求信息requestHeader:true,// 打印请求头requestBody:true,// 打印请求体(POST 数据)responseHeader:true,// 打印响应头responseBody:true,// 打印响应体(敏感数据需注意屏蔽)error:true,// 打印异常信息logPrint:(object){// 自定义日志打印方式(如写入文件、上传到服务器)print("Dio Log: $object");},),);

四、高级用法:请求取消、超时设置与并发控制

在复杂场景(如列表下拉刷新、页面销毁时),需要对请求进行精细化控制,避免无效请求导致的性能问题或数据错乱。

1. 请求取消:CancelToken 用法

Dio 支持通过CancelToken取消单个或多个请求,适用于“页面销毁时取消未完成请求”“快速切换标签时取消前一个请求”等场景:

// 1. 创建 CancelToken 实例finalCancelToken _cancelToken=CancelToken();// 2. 发起请求时关联 CancelTokenFuture<List<User>>getUserList()async{try{returnawaitApiService.getUserList(cancelToken:_cancelToken,// 关联取消令牌);}onDioExceptioncatch(e){if(CancelToken.isCancel(e)){print("请求已取消:${e.message}");}else{throwe;}}}// 3. 取消请求(如页面销毁时)@overridevoiddispose(){_cancelToken.cancel("页面已销毁,取消请求");// 取消请求并添加原因super.dispose();}

2. 超时设置:全局与局部结合

Dio 支持全局超时设置(初始化时配置)与局部超时设置(单个请求单独配置),局部配置会覆盖全局配置:

// 1. 全局超时(已在 DioUtil 初始化时配置)// connectTimeout: Duration(seconds: 5),// receiveTimeout: Duration(seconds: 30),// 2. 局部超时(单个请求单独设置,适用于大文件上传/下载)Future<String>downloadFile({required String url,required String savePath})async{finalresponse=awaitDioUtil().dio.download(url,savePath,options:Options(sendTimeout:constDuration(minutes:5),// 发送超时(大文件上传)receiveTimeout:constDuration(minutes:5),// 接收超时(大文件下载)),);returnsavePath;}

3. 并发控制:限制同时请求数量

在“批量上传文件”“同时发起多个接口请求”等场景,过多并发请求可能导致网络阻塞,可通过dio-queue等第三方库实现并发控制:

// 添加依赖dependencies:dio_queue:^1.0.0// 初始化队列拦截器(限制最大并发数为 3)finalQueueInterceptor queueInterceptor=QueueInterceptor(maxConcurrentRequests:3,// 最大并发请求数);// 添加到 Dio 拦截器DioUtil().dio.interceptors.add(queueInterceptor);

五、异常处理:全面覆盖网络与业务错误

网络请求过程中可能出现多种异常(如网络错误、超时、404、500、业务错误等),需通过统一的异常处理机制提升用户体验。

1. 异常分类与处理思路

将异常分为三类,分别处理:

  • 网络异常:无网络、连接超时、接收超时等,提示用户“检查网络连接”;

  • HTTP 异常:404(接口不存在)、500(服务器错误)、401(未授权)等,根据状态码给出对应提示;

  • 业务异常:后端返回的业务错误(如“用户名或密码错误”“参数校验失败”),直接显示后端返回的错误信息。

2. 统一异常处理实现

在 DioUtil 中封装异常处理方法,并在业务层通过try/catch捕获并处理:

// 1. 定义异常类型枚举(便于业务层判断)enumNetworkErrorType{networkError,// 网络错误timeout,// 超时httpError,// HTTP 错误businessError,// 业务错误cancel,// 请求取消unknown,// 未知错误}// 2. 自定义异常类classNetworkExceptionimplementsException{finalNetworkErrorType type;finalString message;NetworkException({requiredthis.type,requiredthis.message});@overrideStringtoString()=>"NetworkException: $type, message: $message";}// 3. 在 DioUtil 中统一转换异常void_handleDioError(DioException e){if(CancelToken.isCancel(e)){throwNetworkException(type:NetworkErrorType.cancel,message:e.message??"请求已取消");}switch(e.type){caseDioExceptionType.connectionError:throwNetworkException(type:NetworkErrorType.networkError,message:"网络错误,请检查网络连接");caseDioExceptionType.connectionTimeout:caseDioExceptionType.receiveTimeout:caseDioExceptionType.sendTimeout:throwNetworkException(type:NetworkErrorType.timeout,message:"请求超时,请稍后重试");caseDioExceptionType.badResponse:finalint statusCode=e.response?.statusCode??0;throwNetworkException(type:NetworkErrorType.httpError,message:"服务器错误($statusCode),请稍后重试",);default:throwNetworkException(type:NetworkErrorType.unknown,message:e.message??"未知错误");}}// 4. 业务层处理异常Future<void>fetchData()async{try{finaldata=awaitApiService.getUserList();// 处理正常数据}onNetworkExceptioncatch(e){// 根据异常类型显示不同提示switch(e.type){caseNetworkErrorType.networkError:_showToast(e.message);break;caseNetworkErrorType.timeout:_showToast(e.message);break;caseNetworkErrorType.cancel:// 无需提示break;default:_showToast(e.message);}}}

六、总结与最佳实践

Dio 作为 Flutter 网络请求的首选库,其强大的功能与灵活的扩展性能够满足从简单到复杂的所有网络场景需求。结合本文内容,总结以下最佳实践:

  • 采用“工具类 + 接口层 + 模型层”的分层封装方案,提升代码可维护性;

  • 合理使用拦截器实现 Token 管理、日志打印、缓存处理,减少重复代码;

  • 通过CancelToken取消无效请求,避免内存泄漏与数据错乱;

  • 统一异常处理机制,区分网络异常、HTTP 异常与业务异常,提升用户体验;

  • 使用环境变量管理baseUrl,区分开发/测试/生产环境,避免硬编码;

  • 配合json_serializable实现 JSON 序列化,减少手动解析错误。

通过以上方案,可构建一套稳定、高效、易维护的 Flutter 网络请求体系,为应用的后续迭代与扩展奠定坚实基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 21:31:11

DPO微调

&#x1f34b;&#x1f34b;AI学习&#x1f34b;&#x1f34b;&#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主…

作者头像 李华
网站建设 2026/5/12 18:52:53

内容管理系统(CMS)的7个关键特点

一套高效的内容管理系统&#xff08;CMS&#xff09;能帮你节省时间、开辟内容个性化的空间&#xff0c;并提升在线形象——从而改善业务成效。合适的CMS可以保持数字形象井然有序、品牌风格统一&#xff0c;并让内容流程顺畅运转&#xff0c;有助于在营销各个环节吸引并留住潜…

作者头像 李华
网站建设 2026/5/11 15:45:26

Prompt Tuning

&#x1f34b;&#x1f34b;AI学习&#x1f34b;&#x1f34b;&#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主…

作者头像 李华
网站建设 2026/4/30 23:43:54

网盘直链解析工具:零基础实现全平台高速下载

还在为网盘下载速度慢而烦恼吗&#xff1f;这款基于JavaScript开发的网盘直链解析工具能够帮助您获取文件的真实下载地址。项目基于"网盘直链下载助手"6.1.4版本优化&#xff0c;为用户提供更纯净、更高效的使用体验。 【免费下载链接】Online-disk-direct-link-down…

作者头像 李华
网站建设 2026/5/8 23:21:58

LobeChat能否实现AI方丈?禅宗公案讲解与心灵修行引导

LobeChat能否实现AI方丈&#xff1f;禅宗公案讲解与心灵修行引导 在深夜的禅堂里&#xff0c;一盏孤灯下&#xff0c;弟子叩问&#xff1a;“狗子还有佛性也无&#xff1f;” 老方丈不答&#xff0c;只是一声棒喝&#xff1a;“无&#xff01;” 千年后&#xff0c;这个问题或…

作者头像 李华
网站建设 2026/5/11 11:01:03

TPFanCtrl2完整使用指南:ThinkPad双风扇智能控制终极方案

TPFanCtrl2完整使用指南&#xff1a;ThinkPad双风扇智能控制终极方案 【免费下载链接】TPFanCtrl2 ThinkPad Fan Control 2 (Dual Fan) for Windows 10 and 11 项目地址: https://gitcode.com/gh_mirrors/tp/TPFanCtrl2 还在为ThinkPad风扇噪音而烦恼吗&#xff1f;TPFa…

作者头像 李华