Flutter Dio二次封装
'# Flutter Dio二次封装
一、背景与问题
在Flutter开发中,网络请求是核心功能之一。Dio作为一款功能强大的HTTP客户端库,被广泛应用于Flutter项目中。但随着项目规模扩大,开发者常遇到以下问题:
- 重复代码:每个API请求都需要重复配置headers、参数、超时时间等
- 错误处理分散:不同接口的错误处理逻辑混杂在业务代码中
- 配置管理困难:基础配置(如超时时间、baseURL)需要在多个地方维护
- 调试困难:缺乏统一的日志记录和调试接口
通过二次封装Dio,可以解决上述问题,实现以下目标:
- 统一网络请求规范
- 集中处理错误和异常
- 管理配置参数
- 提供调试工具
二、基本原理
Dio的核心机制基于Interceptor(拦截器)和BaseOptions(基础配置)。二次封装主要通过以下方式实现:
- 创建自定义Dio实例:继承Dio类,添加自定义配置
- 添加拦截器:在请求发送前和响应接收后进行处理
- 统一错误处理:集中处理网络错误、业务错误
- 参数封装:将请求参数、headers、超时时间等统一管理
Dio的请求流程如下:
请求发起 -> onRequest拦截器 -> 发送请求 -> onResponse拦截器 -> 接收响应 -> onException拦截器三、环境准备
在pubspec.yaml中添加依赖:
dependencies:
dio: ^5.0.0
http: ^0.13.7需要创建的文件结构:
lib/
├── network/
│ ├── base_dio.dart # 基础Dio封装
│ ├── interceptor.dart # 拦截器实现
│ ├── api.dart # API接口定义
│ └── service.dart # 服务类
├── models/
│ └── response_model.dart # 响应模型
└── main.dart四、核心实现
1. 基础Dio封装(base_dio.dart)
import 'package:dio/dio.dart';
import 'package:http/http.dart' as http;
import 'interceptor.dart';
class BaseDio {
static final _dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
timeout: const Duration(seconds: 10),
connectTimeout: const Duration(seconds: 5),
),
);
static final _interceptor = Interceptor(
onRequest: (options, handler) {
// 添加公共headers
options.headers['Authorization'] = 'Bearer ${token}';
options.headers['Content-Type'] = 'application/json';
return handler.next(options);
},
onResponse: (response, handler) {
// 处理成功响应
return handler.next(response);
},
onError: (err, handler) {
// 处理错误
return handler.next(err);
},
);
static Future<void> init() async {
_dio.interceptors.add(_interceptor);
// 配置http客户端
(dio.client as http.Client).timeout = const Duration(seconds: 10);
}
static Dio get instance => _dio;
}关键点解释:
- 使用
BaseOptions统一配置基础参数 - 拦截器处理请求和响应
- 通过
token变量管理认证信息 - 配置http客户端超时时间
2. 拦截器实现(interceptor.dart)
import 'package:dio/dio.dart';
class Interceptor {
final Function(RequestOptions, RequestHandler) onRequest;
final Function(Response<dynamic>, ResponseHandler) onResponse;
final Function(DioError, ResponseHandler) onError;
Interceptor({
required this.onRequest,
required this.onResponse,
required this.onError,
});
void add(Dio dio) {
dio.interceptors.add(InterceptHandler(
onRequest: onRequest,
onResponse: onResponse,
onError: onError,
));
}
}
class InterceptHandler extends Interceptor {
InterceptHandler({
required Function(RequestOptions, RequestHandler) onRequest,
required Function(Response<dynamic>, ResponseHandler) onResponse,
required Function(DioError, ResponseHandler) onError,
}) :
_onRequest = onRequest,
_onResponse = onResponse,
_onError = onError;
final Function(RequestOptions, RequestHandler) _onRequest;
final Function(Response<dynamic>, ResponseHandler) _onResponse;
final Function(DioError, ResponseHandler) _onError;
@override
void onRequest(RequestOptions options, RequestHandler handler) {
_onRequest(options, handler);
}
@override
void onResponse(Response<dynamic> response, ResponseHandler handler) {
_onResponse(response, handler);
}
@override
void onError(DioError error, ResponseHandler handler) {
_onError(error, handler);
}
}关键点解释:
- 分离拦截器逻辑和具体实现
- 支持自定义请求/响应处理逻辑
- 提供统一的错误处理入口
3. 错误处理封装(service.dart)
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'response_model.dart';
class ApiService {
static Future<void> init() async {
await BaseDio.init();
await SharedPreferences.getInstance();
}
static Future<T> request<T>(String path, {
Map<String, dynamic>? params,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
try {
final response = await BaseDio.instance.get(path,
queryParameters: queryParameters,
options: options,
cancelToken: CancelToken(),
);
return ResponseModel.fromJson<T>(response.data);
} catch (e) {
// 处理错误
if (e is DioError) {
switch (e.type) {
case DioErrorType.CONNECT_TIMEOUT:
throw Exception('连接超时');
case DioErrorType.RESPONSE_TIMEOUT:
throw Exception('响应超时');
case DioErrorType.READ_TIMEOUT:
throw Exception('读取超时');
case DioErrorType.CANCEL:
throw Exception('请求取消');
case DioErrorType.SEND_TIMEOUT:
throw Exception('发送超时');
case DioErrorType.RETRY:
throw Exception('重试失败');
case DioErrorType.SERVER:
throw Exception('服务器错误');
case DioErrorType.CLIENT:
throw Exception('客户端错误');
case DioErrorType.UNKNOWN:
throw Exception('未知错误');
default:
throw Exception('网络错误');
}
}
throw Exception('网络请求失败');
}
}
}关键点解释:
- 封装通用请求方法
- 统一处理各种错误类型
- 支持参数和查询参数
- 使用
CancelToken支持取消请求
五、完整案例
1. 用户登录接口(api.dart)
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserApi {
static Future<void> login(String username, String password) async {
final response = await ApiService.request<Map<String, dynamic>>(
'auth/login',
queryParameters: {
'username': username,
'password': password,
},
);
if (response.isSuccess) {
// 保存token
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', response.data['token']);
} else {
throw Exception('登录失败: ${response.message}');
}
}
}2. 响应模型(response_model.dart)
class ResponseModel<T> {
final bool success;
final String message;
final T? data;
ResponseModel({
required this.success,
required this.message,
this.data,
});
factory ResponseModel.fromJson<T>(Map<String, dynamic> json) {
final success = json['success'] as bool;
final message = json['message'] as String;
final data = json['data'] != null ? json['data'] as T : null;
return ResponseModel<T>(success: success, message: message, data: data);
}
}3. 使用示例(main.dart)
import 'package:flutter/material.dart';
import 'user_api.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dio封装示例',
home: const LoginScreen(),
);
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: '用户名'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(labelText: '密码'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
try {
await UserApi.login(
_usernameController.text,
_passwordController.text,
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('登录成功')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('登录失败: $e')),
);
}
}
},
child: const Text('登录'),
),
],
),
),
),
);
}
}六、源码解析
1. 拦截器处理流程
void onRequest(RequestOptions options, RequestHandler handler) {
// 添加公共headers
options.headers['Authorization'] = 'Bearer ${token}';
options.headers['Content-Type'] = 'application/json';
return handler.next(options);
}options包含请求参数、headers、查询参数等handler.next继续处理流程- 可在此处添加token、签名等公共信息
2. 错误处理逻辑
catch (e) {
if (e is DioError) {
switch (e.type) {
case DioErrorType.CONNECT_TIMEOUT:
throw Exception('连接超时');
// ...其他错误类型处理
}
}
}- 区分不同类型的网络错误
- 统一抛出异常,便于上层处理
- 可记录错误日志或触发错误回调
七、进阶使用
1. 支持Token管理
import 'package:shared_preferences/shared_preferences.dart';
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('token');
}- 在拦截器中动态获取token
- 需要处理token过期、刷新等逻辑
2. 文件上传支持
Future<void> uploadFile(String filePath, String uploadPath) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
});
final response = await ApiService.request<Map<String, dynamic>>(
uploadPath,
data: formData,
options: Options(
contentType: 'multipart/form-data',
),
);
// 处理上传结果
}3. 缓存支持
Future<void> cacheRequest(String path, String cacheKey) async {
final cached = await getCache(cacheKey);
if (cached != null) {
return cached;
}
final response = await ApiService.request(path);
await setCache(cacheKey, response);
return response;
}八、性能与工程实践
1. 性能优化
- 连接池:Dio默认使用连接池,可复用TCP连接
- 缓存策略:对不常变更的接口使用缓存
- 异步处理:使用
await和async/await避免阻塞主线程 - 压缩数据:对大体积数据进行压缩传输
- 连接超时:合理设置超时时间,避免卡顿
2. 安全风险
- HTTPS配置:强制使用HTTPS,配置证书校验
- 敏感信息加密:对token、密码等敏感信息进行加密存储
- 输入验证:对用户输入进行校验,防止注入攻击
- CORS配置:合理配置CORS策略,防止跨域攻击
- 日志安全:避免在日志中记录敏感信息
九、常见问题与踩坑
1. 网络错误处理不当
// 错误示例:未处理DioError
try {
await ApiService.request(...);
} catch (e) {
print('错误: $e');
}问题:无法区分不同类型的错误,影响错误处理逻辑
改进:使用isDioError判断并处理不同错误类型
2. 拦截器冲突
// 错误示例:多个拦截器未正确处理
dio.interceptors.add((options, handler) {
// 未调用handler.next
});问题:拦截器未调用handler.next导致流程中断
改进:确保每个拦截器都调用handler.next
3. 配置未生效
// 错误示例:未正确初始化
final dio = Dio();问题:未调用BaseDio.init()导致配置未生效
改进:确保在应用初始化时调用BaseDio.init()
十、最佳实践
- 统一错误处理:所有网络请求统一处理错误,避免重复代码
- 配置管理:将基础配置集中管理,便于维护
- 拦截器分层:将公共逻辑和业务逻辑分离
- 日志记录:添加调试日志,便于排查问题
- 支持取消请求:添加
CancelToken支持,提升用户体验 - 缓存策略:对不常变更的接口添加缓存
- 安全措施:强制使用HTTPS,加密敏感信息
十一、总结
通过Dio的二次封装,可以显著提升Flutter项目的网络请求质量。本方案实现了以下目标:
- 统一网络请求规范,降低耦合
- 集中处理错误和异常,提高可维护性
- 管理配置参数,便于统一维护
- 提供调试工具,方便问题排查
建议在以下场景使用本方案:
- 中大型项目需要统一网络请求规范
- 需要集中处理错误和异常
- 需要动态配置请求参数和headers
- 需要支持Token管理、缓存等高级功能
但需注意:
- 小型项目或简单请求可能增加复杂度
- 需要合理配置和维护拦截器逻辑
- 需要处理好安全性和性能问题
通过合理使用Dio二次封装,可以显著提升Flutter项目的网络请求质量,使代码更简洁、可维护性更强。在实际开发中,需要根据项目规模和需求选择合适的封装方案,平衡代码简洁性和功能扩展性。
评论已关闭