Flutter Dio二次封装

'# Flutter Dio二次封装

一、背景与问题

在Flutter开发中,网络请求是核心功能之一。Dio作为一款功能强大的HTTP客户端库,被广泛应用于Flutter项目中。但随着项目规模扩大,开发者常遇到以下问题:

  1. 重复代码:每个API请求都需要重复配置headers、参数、超时时间等
  2. 错误处理分散:不同接口的错误处理逻辑混杂在业务代码中
  3. 配置管理困难:基础配置(如超时时间、baseURL)需要在多个地方维护
  4. 调试困难:缺乏统一的日志记录和调试接口

通过二次封装Dio,可以解决上述问题,实现以下目标:

  • 统一网络请求规范
  • 集中处理错误和异常
  • 管理配置参数
  • 提供调试工具

二、基本原理

Dio的核心机制基于Interceptor(拦截器)和BaseOptions(基础配置)。二次封装主要通过以下方式实现:

  1. 创建自定义Dio实例:继承Dio类,添加自定义配置
  2. 添加拦截器:在请求发送前和响应接收后进行处理
  3. 统一错误处理:集中处理网络错误、业务错误
  4. 参数封装:将请求参数、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. 性能优化

  1. 连接池:Dio默认使用连接池,可复用TCP连接
  2. 缓存策略:对不常变更的接口使用缓存
  3. 异步处理:使用await和async/await避免阻塞主线程
  4. 压缩数据:对大体积数据进行压缩传输
  5. 连接超时:合理设置超时时间,避免卡顿

2. 安全风险

  1. HTTPS配置:强制使用HTTPS,配置证书校验
  2. 敏感信息加密:对token、密码等敏感信息进行加密存储
  3. 输入验证:对用户输入进行校验,防止注入攻击
  4. CORS配置:合理配置CORS策略,防止跨域攻击
  5. 日志安全:避免在日志中记录敏感信息

九、常见问题与踩坑

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()

十、最佳实践

  1. 统一错误处理:所有网络请求统一处理错误,避免重复代码
  2. 配置管理:将基础配置集中管理,便于维护
  3. 拦截器分层:将公共逻辑和业务逻辑分离
  4. 日志记录:添加调试日志,便于排查问题
  5. 支持取消请求:添加CancelToken支持,提升用户体验
  6. 缓存策略:对不常变更的接口添加缓存
  7. 安全措施:强制使用HTTPS,加密敏感信息

十一、总结

通过Dio的二次封装,可以显著提升Flutter项目的网络请求质量。本方案实现了以下目标:

  • 统一网络请求规范,降低耦合
  • 集中处理错误和异常,提高可维护性
  • 管理配置参数,便于统一维护
  • 提供调试工具,方便问题排查

建议在以下场景使用本方案:

  • 中大型项目需要统一网络请求规范
  • 需要集中处理错误和异常
  • 需要动态配置请求参数和headers
  • 需要支持Token管理、缓存等高级功能

但需注意:

  • 小型项目或简单请求可能增加复杂度
  • 需要合理配置和维护拦截器逻辑
  • 需要处理好安全性和性能问题

通过合理使用Dio二次封装,可以显著提升Flutter项目的网络请求质量,使代码更简洁、可维护性更强。在实际开发中,需要根据项目规模和需求选择合适的封装方案,平衡代码简洁性和功能扩展性。

none
最后修改于:2026年10月07日 19:18

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日