前言:
这一章是比较重要的交互,实际上是比较通用的代码,就是进行http请求和本地缓存的一些库,与后端交互或者纯app请求一些tts、转录之类的模型,都是需要的,跑不掉。
一、总览
本讲聚焦Flutter开发中网络通信与数据处理的核心能力,解决以下关键问题:
- 统一管理网络请求(GET/POST),避免重复代码
- 处理请求头、Token认证、超时、异常等网络边界场景
- 规范化JSON数据解析,避免手动解析的错误
- 实现网络缓存、离线可用等提升用户体验的策略
- 通过Dio封装和拦截器实现请求/响应的全局管控
让你的Flutter应用能稳定、高效、安全地与后端交互,并优雅处理数据。
- 分层设计:业务层不直接接触原生网络层,通过封装的Dio工具类解耦
- 拦截器核心:请求发出/响应返回时的"中间件",统一处理通用逻辑
- 数据流转:网络响应→拦截器处理→JSON解析→模型类→业务层(可结合缓存)
- 异常闭环:所有网络错误在拦截器/工具类中统一捕获,避免业务层零散处理
二、核心技术拆解
1. Dio封装与拦截器
添加Dio依赖
1.1 核心属性说明
| 属性/方法 | 作用 | 常用值/场景 |
|---|---|---|
BaseOptions | Dio基础配置 | baseUrl(接口前缀)、connectTimeout(连接超时)、headers(默认头) |
interceptors | 拦截器列表 | RequestInterceptor(请求拦截)、ResponseInterceptor(响应拦截) |
get/post | 请求方法 | queryParameters(URL参数)、data(POST请求体) |
DioException | 异常类型 | connectionTimeout(超时)、badResponse(服务端错误) |
1.2 基础封装
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
/// 全局Dio封装类
class HttpManager {
static final HttpManager _instance = HttpManager._internal();
factory HttpManager() => _instance;
late Dio _dio;
// 私有构造函数
HttpManager._internal() {
// 初始化Dio配置
BaseOptions options = BaseOptions(
baseUrl: "https://api.example.com", // 基础地址
connectTimeout: const Duration(seconds: 10), // 连接超时
receiveTimeout: const Duration(seconds: 5), // 接收超时
headers: {
"Content-Type": "application/json",
"version": "1.0.0"
}, // 默认请求头
);
_dio = Dio(options);
// 添加拦截器
_addInterceptors();
}
// 添加拦截器
void _addInterceptors() {
// 1. 请求拦截器:添加Token、日志
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
// 添加Token
String? token = _getToken();
if (token != null) {
options.headers["Authorization"] = "Bearer $token";
}
// 开发环境打印请求日志
if (kDebugMode) {
print("请求URL:${options.uri}");
print("请求参数:${options.data}");
}
handler.next(options); // 继续执行
},
// 2. 响应拦截器:统一处理响应、错误
onResponse: (Response response, ResponseInterceptorHandler handler) {
if (kDebugMode) {
print("响应数据:${response.data}");
}
handler.next(response);
},
// 3. 错误拦截器:统一处理异常
onError: (DioException e, ErrorInterceptorHandler handler) {
if (kDebugMode) {
print("请求错误:${e.message}");
}
// 错误处理逻辑(重试/提示)
_handleError(e, handler);
},
));
// 可选:添加日志拦截器(更详细)
if (kDebugMode) {
_dio.interceptors.add(LogInterceptor(responseBody: true));
}
}
// 获取本地Token(示例)
String? _getToken() {
// 实际项目中从SharedPreferences等存储中获取
return "your_token_here";
}
// 错误处理与重试
void _handleError(DioException e, ErrorInterceptorHandler handler) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
// 超时错误,可实现重试逻辑
_retryRequest(e.requestOptions, handler);
break;
case DioExceptionType.badResponse:
// 服务器错误(4xx/5xx)
if (e.response?.statusCode == 401) {
// Token过期,跳转登录页
// Navigator.pushReplacementNamed(context, "/login");
}
handler.next(e);
break;
default:
handler.next(e);
}
}
// 重试请求
void _retryRequest(RequestOptions options, ErrorInterceptorHandler handler) {
// 简单重试逻辑:最多重试1次
int retryCount = 0;
if (retryCount < 1) {
retryCount++;
_dio.request(options.path, data: options.data).then((response) {
handler.resolve(response);
retryCount = 0;
}).catchError((error) {
handler.next(error as DioException);
retryCount = 0;
});
} else {
retryCount = 0;
}
}
// 暴露GET请求方法
Future<T> get<T>(
String path, {
Map<String, dynamic>? params,
Options? options,
}) async {
try {
Response response = await _dio.get(path, queryParameters: params, options: options);
return response.data as T;
} on DioException catch (e) {
throw _convertError(e);
}
}
// 暴露POST请求方法
Future<T> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? params,
Options? options,
}) async {
try {
Response response = await _dio.post(
path,
data: data,
queryParameters: params,
options: options,
);
return response.data as T;
} on DioException catch (e) {
throw _convertError(e);
}
}
// 统一错误转换
String _convertError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return "网络连接超时,请检查网络";
case DioExceptionType.receiveTimeout:
return "数据接收超时";
case DioExceptionType.badResponse:
return "服务器错误(${e.response?.statusCode})";
case DioExceptionType.connectionError:
return "网络连接失败,请检查网络";
default:
return e.message ?? "未知错误";
}
}
// 获取原始Dio实例(特殊场景使用)
Dio get dio => _dio;
}
1.3 注意事项
- 拦截器中必须调用
handler.next()/handler.resolve()/handler.reject(),否则请求会卡住 - Token过期处理需结合路由(需确保上下文可用,或使用全局状态管理)
- 重试逻辑要限制次数,避免无限重试
- 生产环境需关闭日志拦截器,防止敏感信息泄露
2. JSON序列化(json_serializable)
(1)配置依赖(pubspec.yaml)
dependencies:
json_annotation: ^4.11.0
dev_dependencies:
json_serializable: ^6.9.0
build_runner: ^2.4.15
(2)模型类案例
import 'package:json_annotation/json_annotation.dart';
// 生成的文件命名:当前文件命.g.dart
part 'user_model.g.dart';
/// 用户模型类
@JsonSerializable()
class UserModel {
// 字段名与JSON字段一致(不一致可通过@JsonKey指定)
final int id;
final String name;
final String email;
// 可选字段(JSON中可能不存在)
@JsonKey(defaultValue: "")
final String avatar;
// 自定义字段映射(JSON字段是create_time,模型中是createTime)
@JsonKey(name: "create_time")
final String createTime;
UserModel({
required this.id,
required this.name,
required this.email,
required this.avatar,
required this.createTime,
});
// 从JSON解析为模型(自动生成)
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
// 模型转换为JSON(自动生成)
Map<String, dynamic> toJson() => _$UserModelToJson(this);
}
(3)生成序列化代码
执行终端命令:
flutter pub run build_runner build
# 或监听文件变化自动生成
flutter pub run build_runner watch
(4)注意事项
- 模型类必须添加
@JsonSerializable()注解 part 'xxx.g.dart'必须与文件名一致- 可选字段需通过
@JsonKey(defaultValue: ...)指定默认值,避免解析空值报错 - 嵌套模型类也需要添加注解并实现序列化方法
3. 缓存与离线策略
(1)核心思路
- 缓存网络响应数据到本地(SharedPreferences/Hive)
- 请求时先读缓存(展示旧数据),再请求网络(更新数据)
- 可设置缓存过期时间,避免展示过期数据
(2)缓存工具类案例
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
class CacheManager {
static final CacheManager _instance = CacheManager._internal();
factory CacheManager() => _instance;
CacheManager._internal();
late SharedPreferences _prefs;
// 初始化
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// 保存缓存(带过期时间,单位:秒)
Future<void> setCache(String key, dynamic data, {int expireSeconds = 300}) async {
final cacheData = {
"data": data,
"timestamp": DateTime.now().millisecondsSinceEpoch,
"expire": expireSeconds * 1000
};
await _prefs.setString(key, json.encode(cacheData));
}
// 获取缓存
dynamic getCache(String key) {
String? cacheStr = _prefs.getString(key);
if (cacheStr == null) return null;
Map<String, dynamic> cacheData = json.decode(cacheStr);
int timestamp = cacheData["timestamp"];
int expire = cacheData["expire"];
// 检查是否过期
if (DateTime.now().millisecondsSinceEpoch - timestamp > expire) {
removeCache(key); // 删除过期缓存
return null;
}
return cacheData["data"];
}
// 删除缓存
Future<void> removeCache(String key) async {
await _prefs.remove(key);
}
// 清空所有缓存
Future<void> clearAllCache() async {
await _prefs.clear();
}
}
三、综合应用案例
需求场景
实现一个"用户信息"模块,包含:
- 获取用户列表(GET请求,带Token)
- 提交用户信息(POST请求)
- 数据序列化到模型类
- 异常处理、超时重试
- 离线缓存用户列表
步骤1:初始化工具类
// 在main.dart中初始化
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化缓存
await CacheManager().init();
runApp(const MyApp());
}
步骤2:定义API接口类
/// 业务API封装
class UserApi {
static final HttpManager _http = HttpManager();
static final CacheManager _cache = CacheManager();
// 1. 获取用户列表(带缓存)
Future<List<UserModel>> getUserList({int page = 1, int size = 10}) async {
String cacheKey = "user_list_$page";
// 1. 先读缓存(离线展示)
dynamic cacheData = _cache.getCache(cacheKey);
if (cacheData != null) {
// 缓存数据转换为模型
List<UserModel> cacheList = (cacheData as List)
.map((e) => UserModel.fromJson(e))
.toList();
// 先返回缓存,再异步请求更新
Future.delayed(Duration.zero, () => _fetchUserList(page, size));
return cacheList;
}
// 2. 无缓存则请求网络
return _fetchUserList(page, size);
}
// 实际请求用户列表
Future<List<UserModel>> _fetchUserList(int page, int size) async {
String cacheKey = "user_list_$page";
try {
// GET请求
Map<String, dynamic> response = await _http.get(
"/users",
params: {"page": page, "size": size},
);
// 解析为模型列表
List<UserModel> userList = (response["data"] as List)
.map((e) => UserModel.fromJson(e))
.toList();
// 保存缓存(5分钟过期)
_cache.setCache(
cacheKey,
response["data"],
expireSeconds: 300,
);
return userList;
} catch (e) {
throw e.toString();
}
}
// 2. 提交用户信息(POST请求)
Future<bool> submitUserInfo(UserModel user) async {
try {
// POST请求:模型转JSON
Map<String, dynamic> response = await _http.post(
"/users/save",
data: user.toJson(),
);
return response["success"] == true;
} catch (e) {
throw e.toString();
}
}
}
步骤3:业务页面使用
import 'package:flutter/material.dart';
class UserPage extends StatefulWidget {
const UserPage({super.key});
@override
State<UserPage> createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
List<UserModel> _userList = [];
bool _isLoading = true;
String? _errorMsg;
@override
void initState() {
super.initState();
_loadUserList();
}
// 加载用户列表
Future<void> _loadUserList() async {
setState(() {
_isLoading = true;
_errorMsg = null;
});
try {
List<UserModel> list = await UserApi().getUserList(page: 1);
setState(() {
_userList = list;
});
} catch (e) {
setState(() {
_errorMsg = e.toString();
});
} finally {
setState(() {
_isLoading = false;
});
}
}
// 提交用户信息示例
Future<void> _submitUser() async {
UserModel newUser = UserModel(
id: 0, // 新增用户ID为0
name: "张三",
email: "zhangsan@example.com",
avatar: "",
createTime: DateTime.now().toString(),
);
try {
bool success = await UserApi().submitUserInfo(newUser);
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("提交成功")),
);
_loadUserList(); // 重新加载列表
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("提交失败:$e"), backgroundColor: Colors.red),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("用户管理")),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: _submitUser,
child: const Icon(Icons.add),
),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_errorMsg != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_errorMsg!),
ElevatedButton(
onPressed: _loadUserList,
child: const Text("重新加载"),
),
],
),
);
}
return ListView.builder(
itemCount: _userList.length,
itemBuilder: (context, index) {
UserModel user = _userList[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Text(user.createTime),
);
},
);
}
}
核心关键点
- Dio封装:通过单例模式统一管理Dio配置,拦截器处理Token、日志、异常、重试等通用逻辑
- JSON序列化:使用
json_serializable自动生成解析代码,避免手动解析的错误,通过@JsonKey处理字段映射 - 完整链路:业务层→封装的Http工具→拦截器→网络请求→数据解析→缓存→业务层,实现请求-解析-缓存的闭环
- 离线策略:先读缓存展示数据,再异步请求更新,提升用户体验,同时设置缓存过期时间保证数据新鲜度
工程化建议
- 所有API接口统一放在单独的
api目录,按业务模块拆分 - 模型类放在
models目录,与API一一对应 - 网络错误提示统一封装为Toast/SnackBar,避免重复代码
- 生产环境关闭日志拦截器,添加接口加密/签名(可选)