Flutter网络请求进阶:离线缓存与弱网适配实战
Flutter网络请求进阶:离线缓存与弱网适配实战
在移动应用场景中,网络环境的不确定性(如弱网、断网、网络切换)是影响用户体验的关键因素。仅靠基础的网络请求封装,无法解决离线状态下的数据访问、弱网环境的请求超时与重试、网络切换后的状态同步等问题。本文将聚焦“离线缓存”与“弱网适配”两大核心需求,展开四大实战内容:分层缓存架构设计、基于Hive的持久化缓存实现、弱网环境的请求优化(超时控制+智能重试)、网络状态感知与离线交互适配,帮助开发者构建“离线可用、弱网流畅”的Flutter网络交互体系。
一、核心认知:离线缓存与弱网适配的设计原则
在动手实现前,需明确两大核心设计原则,避免陷入“缓存混乱”“重试无效”等坑:
-
缓存分层原则:采用“内存缓存+持久化缓存”双层架构。内存缓存用于存储高频访问的临时数据(如当前页面数据),优势是读取速度快;持久化缓存用于存储需要离线访问的核心数据(如用户信息、历史列表),优势是进程重启后不丢失。
-
弱网适配核心原则:“容错+智能”结合。容错指合理的超时控制、请求失败后的友好提示;智能指根据网络类型动态调整策略(如弱网延长超时时间、WiFi环境缩短重试间隔)、避免无效重试(如明确失败的请求不重复发起)。
-
数据一致性原则:明确缓存有效期与更新机制。对于实时性要求高的数据(如订单状态),缓存有效期短且需主动同步;对于静态数据(如分类列表),可延长缓存有效期降低网络依赖。
二、分层缓存架构实现:内存缓存+Hive持久化缓存
本节将实现一套可扩展的分层缓存架构,支持缓存策略配置(如是否缓存、缓存有效期、缓存key生成规则),同时兼容不同类型的请求(GET/POST)与数据格式(JSON/二进制)。
1. 基础准备:定义缓存模型与策略枚举
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
// 缓存策略枚举
enum CacheStrategy {
noCache, // 不缓存
memoryOnly, // 仅内存缓存
persistent, // 持久化缓存(内存+持久化)
refreshAndCache, // 先请求刷新,再缓存(用于实时性数据)
}
// 缓存数据模型(持久化用)
class CacheEntity extends HiveObject {
// 缓存key(唯一标识)
final String cacheKey;
// 缓存数据(JSON字符串)
final String data;
// 数据类型(如"json"、"binary")
final String dataType;
// 缓存创建时间(时间戳,毫秒级)
final int createTime;
// 缓存有效期(毫秒级,-1表示永久有效)
final int maxAge;
// 最后访问时间(用于内存缓存LRU淘汰)
int lastAccessTime;
CacheEntity({
required this.cacheKey,
required this.data,
required this.dataType,
required this.createTime,
required this.maxAge,
required this.lastAccessTime,
});
// 转换为Map(用于Hive序列化,Hive支持基本类型与自定义对象)
Map<String, dynamic> toMap() {
return {
'cacheKey': cacheKey,
'data': data,
'dataType': dataType,
'createTime': createTime,
'maxAge': maxAge,
'lastAccessTime': lastAccessTime,
};
}
// 从Map构建对象
factory CacheEntity.fromMap(Map<String, dynamic> map) {
return CacheEntity(
cacheKey: map['cacheKey'] as String,
data: map['data'] as String,
dataType: map['dataType'] as String,
createTime: map['createTime'] as int,
maxAge: map['maxAge'] as int,
lastAccessTime: map['lastAccessTime'] as int,
);
}
// 检查缓存是否有效
bool get isExpired {
if (maxAge == -1) return false; // 永久有效
final now = DateTime.now().millisecondsSinceEpoch;
return now - createTime > maxAge;
}
}
// 注册Hive适配器(使Hive支持CacheEntity序列化)
class CacheEntityAdapter extends TypeAdapter<CacheEntity> {
@override
final typeId = 1; // 唯一标识,需与其他自定义对象不重复
@override
CacheEntity read(BinaryReader reader) {
final map = reader.readMap();
return CacheEntity.fromMap(map);
}
@override
void write(BinaryWriter writer, CacheEntity obj) {
writer.writeMap(obj.toMap());
}
}
2. 实现内存缓存:基于LRU算法的临时缓存
内存缓存采用LRU(最近最少使用)算法,当缓存容量达到上限时,自动淘汰最久未访问的缓存项,避免内存溢出。
import 'dart:collection';
// 内存缓存管理器(单例,LRU算法)
class MemoryCacheManager {
static final MemoryCacheManager _instance = MemoryCacheManager._internal();
factory MemoryCacheManager() => _instance;
MemoryCacheManager._internal();
// 缓存容量(默认100条,可动态调整)
int _capacity = 100;
// LRU缓存容器(LinkedHashMap保持插入顺序,便于实现LRU)
final LinkedHashMap<String, CacheEntity> _cache = LinkedHashMap();
// 设置缓存容量
void setCapacity(int capacity) {
if (capacity > 0) {
_capacity = capacity;
_trimCache(); // 调整后裁剪超出容量的缓存
}
}
// 添加缓存
void put(String cacheKey, CacheEntity entity) {
// 更新最后访问时间
entity.lastAccessTime = DateTime.now().millisecondsSinceEpoch;
// 若已存在,先移除(保证新添加的在末尾,视为最近使用)
if (_cache.containsKey(cacheKey)) {
_cache.remove(cacheKey);
}
// 添加到缓存
_cache[cacheKey] = entity;
// 裁剪缓存
_trimCache();
}
// 获取缓存
CacheEntity? get(String cacheKey) {
final entity = _cache[cacheKey];
if (entity == null) return null;
// 检查是否过期
if (entity.isExpired) {
remove(cacheKey);
return null;
}
// 更新最后访问时间(标记为最近使用)
entity.lastAccessTime = DateTime.now().millisecondsSinceEpoch;
// 移到末尾,保持LRU顺序
_cache.remove(cacheKey);
_cache[cacheKey] = entity;
return entity;
}
// 移除指定缓存
void remove(String cacheKey) {
_cache.remove(cacheKey);
}
// 清空所有缓存
void clear() {
_cache.clear();
}
// 裁剪缓存(移除最久未访问的项,直到容量符合要求)
void _trimCache() {
while (_cache.length > _capacity) {
// 移除第一个元素(最久未访问)
final firstKey = _cache.keys.first;
_cache.remove(firstKey);
}
}
// 获取当前缓存大小
int get size => _cache.length;
}
final memoryCache = MemoryCacheManager();
3. 实现持久化缓存:基于Hive的本地存储
Hive是Flutter生态中轻量、高效的NoSQL数据库,支持自定义对象序列化,比SharedPreferences更适合存储复杂的缓存数据。本节将基于Hive实现持久化缓存。
步骤1:添加依赖与初始化Hive
// pubspec.yaml
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0 # 适配Flutter的Hive扩展
path_provider: ^2.1.1 # 获取本地存储路径
dev_dependencies:
hive_generator: ^1.1.5
build_runner: ^2.4.4
// main.dart 初始化Hive
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'cache_entity.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. 获取本地存储路径
final appDocDir = await getApplicationDocumentsDirectory();
Hive.initFlutter(appDocDir.path);
// 2. 注册CacheEntity适配器(必须在打开盒子前注册)
Hive.registerAdapter(CacheEntityAdapter());
// 3. 打开缓存盒子(相当于数据库表)
await Hive.openBox<CacheEntity>('network_cache_box');
// ... 其他初始化逻辑(如网络监听、签名工具)
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '离线缓存与弱网适配示例',
home: const HomePage(),
);
}
}
步骤2:实现持久化缓存管理器
// persistent_cache_manager.dart
import 'package:hive/hive.dart';
import 'cache_entity.dart';
// 持久化缓存管理器(单例)
class PersistentCacheManager {
static final PersistentCacheManager _instance = PersistentCacheManager._internal();
factory PersistentCacheManager() => _instance;
PersistentCacheManager._internal();
// 缓存盒子(从Hive获取,已在main中初始化)
late Box<CacheEntity> _cacheBox;
// 初始化(必须在Hive打开盒子后调用)
void init() {
_cacheBox = Hive.box<CacheEntity>('network_cache_box');
// 清理过期缓存(应用启动时执行,避免过期数据堆积)
_cleanExpiredCache();
}
// 添加缓存
void put(String cacheKey, CacheEntity entity) {
_cacheBox.put(cacheKey, entity);
}
// 获取缓存
CacheEntity? get(String cacheKey) {
final entity = _cacheBox.get(cacheKey);
if (entity == null) return null;
// 检查是否过期
if (entity.isExpired) {
remove(cacheKey);
return null;
}
// 更新最后访问时间
entity.lastAccessTime = DateTime.now().millisecondsSinceEpoch;
put(cacheKey, entity); // 重新存入(更新数据)
return entity;
}
// 移除指定缓存
void remove(String cacheKey) {
_cacheBox.delete(cacheKey);
}
// 清空所有缓存
void clear() {
_cacheBox.clear();
}
// 清理所有过期缓存
void _cleanExpiredCache() {
final allKeys = _cacheBox.keys.toList();
for (final key in allKeys) {
final entity = _cacheBox.get(key);
if (entity != null && entity.isExpired) {
_cacheBox.delete(key);
}
}
}
// 获取缓存总数
int get size => _cacheBox.length;
}
final persistentCache = PersistentCacheManager();
4. 整合分层缓存:实现统一缓存管理器
封装统一的缓存管理器,对外提供统一的API,屏蔽内存缓存与持久化缓存的底层差异,方便上层调用。
// cache_manager.dart
import 'dart:convert';
import 'cache_entity.dart';
import 'memory_cache.dart';
import 'persistent_cache.dart';
// 统一缓存管理器(单例)
class CacheManager {
static final CacheManager _instance = CacheManager._internal();
factory CacheManager() => _instance;
CacheManager._internal();
// 初始化(调用此方法完成所有缓存初始化)
void init() {
persistentCache.init();
// 可在这里设置内存缓存容量(根据业务调整)
memoryCache.setCapacity(50);
}
// 生成缓存key(基于请求信息,确保唯一)
String generateCacheKey({
required String method,
required String path,
Map<String, dynamic>? queryParams,
dynamic bodyParams,
}) {
// 排序参数,避免因参数顺序不同导致key不一致
final sortedQueryParams = _sortParams(queryParams ?? {});
final sortedBodyParams = bodyParams is Map ? _sortParams(bodyParams) : json.encode(bodyParams);
// 拼接key:method+path+query+body
return '$method\_$path\_$sortedQueryParams\_$sortedBodyParams';
}
// 排序参数(按key升序)
String _sortParams(Map<String, dynamic> params) {
final sortedKeys = params.keys.toList()..sort();
final sortedMap = <String, dynamic>{};
for (final key in sortedKeys) {
sortedMap[key] = params[key];
}
return json.encode(sortedMap);
}
// 缓存数据(根据策略存储)
void cacheData({
required String method,
required String path,
Map<String, dynamic>? queryParams,
dynamic bodyParams,
required dynamic data,
required CacheStrategy strategy,
int maxAge = 300000, // 默认有效期5分钟(毫秒)
}) {
if (strategy == CacheStrategy.noCache) return;
// 生成缓存key
final cacheKey = generateCacheKey(
method: method,
path: path,
queryParams: queryParams,
bodyParams: bodyParams,
);
// 转换数据为JSON字符串
final dataStr = json.encode(data);
final now = DateTime.now().millisecondsSinceEpoch;
// 创建缓存实体
final entity = CacheEntity(
cacheKey: cacheKey,
data: dataStr,
dataType: 'json',
createTime: now,
maxAge: maxAge,
lastAccessTime: now,
);
// 根据策略存储
if (strategy == CacheStrategy.memoryOnly) {
memoryCache.put(cacheKey, entity);
} else if (strategy == CacheStrategy.persistent || strategy == CacheStrategy.refreshAndCache) {
// 持久化缓存同时存入内存,提升读取速度
memoryCache.put(cacheKey, entity);
persistentCache.put(cacheKey, entity);
}
}
// 获取缓存数据(优先内存缓存,再持久化缓存)
dynamic getCachedData({
required String method,
required String path,
Map<String, dynamic>? queryParams,
dynamic bodyParams,
}) {
// 生成缓存key
final cacheKey = generateCacheKey(
method: method,
path: path,
queryParams: queryParams,
bodyParams: bodyParams,
);
// 1. 从内存缓存获取
final memoryEntity = memoryCache.get(cacheKey);
if (memoryEntity != null) {
return json.decode(memoryEntity.data);
}
// 2. 从持久化缓存获取
final persistentEntity = persistentCache.get(cacheKey);
if (persistentEntity != null) {
// 存入内存缓存,提升下次访问速度
memoryCache.put(cacheKey, persistentEntity);
return json.decode(persistentEntity.data);
}
// 无缓存
return null;
}
// 移除指定请求的缓存
void removeCachedData({
required String method,
required String path,
Map<String, dynamic>? queryParams,
dynamic bodyParams,
}) {
final cacheKey = generateCacheKey(
method: method,
path: path,
queryParams: queryParams,
bodyParams: bodyParams,
);
memoryCache.remove(cacheKey);
persistentCache.remove(cacheKey);
}
// 清空所有缓存
void clearAllCache() {
memoryCache.clear();
persistentCache.clear();
}
}
final cacheManager = CacheManager();
5. 在NetworkUtil中集成缓存拦截器
通过Dio拦截器实现缓存的自动拦截与存储,无需在每个请求中手动调用缓存API。
// cache_interceptor.dart
import 'package:dio/dio.dart';
import 'cache_manager.dart';
import 'cache_entity.dart';
// 缓存拦截器(请求时先查缓存,响应时存储缓存)
class CacheInterceptor extends Interceptor {
// 全局默认缓存策略(可在单个请求中覆盖)
final CacheStrategy defaultStrategy;
// 全局默认缓存有效期(毫秒)
final int defaultMaxAge;
CacheInterceptor({
this.defaultStrategy = CacheStrategy.noCache,
this.defaultMaxAge = 300000,
});
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
// 1. 获取当前请求的缓存策略(优先使用请求自带的,无则用全局默认)
final strategy = options.extra['cacheStrategy'] as CacheStrategy? ?? defaultStrategy;
if (strategy == CacheStrategy.noCache) {
handler.next(options);
return;
}
// 2. 对于refreshAndCache策略,直接请求(先刷新再缓存)
if (strategy == CacheStrategy.refreshAndCache) {
handler.next(options);
return;
}
// 3. 其他策略(memoryOnly/persistent),先查缓存
final cachedData = cacheManager.getCachedData(
method: options.method,
path: options.path,
queryParams: options.queryParameters,
bodyParams: options.data,
);
if (cachedData != null) {
// 有缓存,直接返回缓存数据(终止网络请求)
handler.resolve(
Response(
data: cachedData,
statusCode: 200,
requestOptions: options,
),
);
} else {
// 无缓存,继续发起网络请求
handler.next(options);
}
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
// 1. 获取当前请求的缓存策略
final strategy = response.requestOptions.extra['cacheStrategy'] as CacheStrategy? ?? defaultStrategy;
if (strategy == CacheStrategy.noCache) {
handler.next(response);
return;
}
// 2. 获取当前请求的缓存有效期
final maxAge = response.requestOptions.extra['cacheMaxAge'] as int? ?? defaultMaxAge;
// 3. 存储缓存(排除错误响应)
if (response.statusCode == 200) {
cacheManager.cacheData(
method: response.requestOptions.method,
path: response.requestOptions.path,
queryParams: response.requestOptions.queryParameters,
bodyParams: response.requestOptions.data,
data: response.data,
strategy: strategy,
maxAge: maxAge,
);
}
handler.next(response);
}
}
// 在NetworkUtil中初始化缓存拦截器
// network_util.dart
class NetworkUtil {
static final NetworkUtil _instance = NetworkUtil._internal();
factory NetworkUtil() => _instance;
late Dio _dio;
NetworkUtil._internal() {
_initDio();
}
void _initDio() {
_dio = Dio();
// ... 其他基础配置(日志、签名等拦截器)
// 初始化缓存管理器
cacheManager.init();
// 添加缓存拦截器(建议在最前面,优先查缓存)
_dio.interceptors.add(
CacheInterceptor(
defaultStrategy: CacheStrategy.persistent, // 全局默认持久化缓存
defaultMaxAge: 300000, // 全局默认5分钟有效期
),
);
// ... 其他拦截器(加密、并发控制等)
}
// 封装带缓存策略的请求方法
Future<T?> requestWithCache<T>(
String path, {
required String method,
Map<String, dynamic>? queryParams,
dynamic data,
Options? options,
CacheStrategy? cacheStrategy,
int? cacheMaxAge,
}) async {
final extra = <String, dynamic>{};
if (cacheStrategy != null) {
extra['cacheStrategy'] = cacheStrategy;
}
if (cacheMaxAge != null) {
extra['cacheMaxAge'] = cacheMaxAge;
}
final requestOptions = Options(
method: method,
...options,
extra: {
...options?.extra ?? {},
...extra,
},
);
try {
final response = await _dio.request(
path,
queryParameters: queryParams,
data: data,
options: requestOptions,
);
return response.data as T?;
} catch (e) {
if (e is DioException) {
print('请求失败:${e.message}');
}
rethrow;
}
}
// 封装带缓存的GET/POST方法
Future<T?> getWithCache<T>(
String path, {
Map<String, dynamic>? queryParams,
Options? options,
CacheStrategy? cacheStrategy,
int? cacheMaxAge,
}) =>
requestWithCache<T>(
path,
method: 'GET',
queryParams: queryParams,
options: options,
cacheStrategy: cacheStrategy,
cacheMaxAge: cacheMaxAge,
);
Future<T?> postWithCache<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParams,
Options? options,
CacheStrategy? cacheStrategy,
int? cacheMaxAge,
}) =>
requestWithCache<T>(
path,
method: 'POST',
data: data,
queryParams: queryParams,
options: options,
cacheStrategy: cacheStrategy,
cacheMaxAge: cacheMaxAge,
);
// ... 其他方法(取消请求、清除缓存等)
}
final netUtil = NetworkUtil();
三、弱网适配:超时控制与智能重试实现
弱网环境的核心问题是“请求超时”与“请求失败”,本节将实现“动态超时控制”与“智能重试”两大功能,提升弱网环境下的请求成功率。
1. 基础:网络状态感知工具类
要实现智能适配,首先需要感知当前网络状态(无网、弱网、WiFi)。基于connectivity_plus库实现网络状态监听。
// network_monitor.dart
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
// 网络状态枚举
enum NetworkState {
disconnected, // 无网络
weakMobile, // 弱网(移动网络)
mobile, // 移动网络(4G/5G)
wifi, // WiFi
unknown, // 未知
}
// 网络状态监听管理器(单例)
class NetworkMonitor {
static final NetworkMonitor _instance = NetworkMonitor._internal();
factory NetworkMonitor() => _instance;
NetworkMonitor._internal();
final Connectivity _connectivity = Connectivity();
late StreamSubscription<ConnectivityResult> _subscription;
NetworkState _currentState = NetworkState.unknown;
// 获取当前网络状态
NetworkState get currentState => _currentState;
// 初始化网络监听
void init() {
// 初始获取一次网络状态
_checkNetworkState();
// 监听网络状态变化
_subscription = _connectivity.onConnectivityChanged.listen((result) {
_updateNetworkState(result);
});
}
// 检查初始网络状态
Future<void> _checkNetworkState() async {
final result = await _connectivity.checkConnectivity();
_updateNetworkState(result);
}
// 更新网络状态
void _updateNetworkState(ConnectivityResult result) {
switch (result) {
case ConnectivityResult.none:
_currentState = NetworkState.disconnected;
break;
case ConnectivityResult.mobile:
// 简单判断:实际项目中可通过网速测试进一步区分弱网/正常移动网络
_currentState = NetworkState.mobile;
// 若需要更精准的弱网判断,可集成speed_test库测试网速
// await _testNetworkSpeed();
break;
case ConnectivityResult.wifi:
_currentState = NetworkState.wifi;
break;
default:
_currentState = NetworkState.unknown;
}
if (kDebugMode) {
print('当前网络状态:${_currentState.name}');
}
}
// 测试网络速度(可选,用于精准判断弱网)
Future<void> _testNetworkSpeed() async {
// 示例:通过请求小文件测试下载速度
// final startTime = DateTime.now().millisecondsSinceEpoch;
// try {
// await Dio().get('https://xxx.com/test.txt');
// final endTime = DateTime.now().millisecondsSinceEpoch;
// final duration = endTime - startTime;
// if (duration > 1000) { // 1KB文件下载超过1秒,视为弱网
// _currentState = NetworkState.weakMobile;
// }
// } catch (e) {
// _currentState = NetworkState.weakMobile;
// }
}
// 取消网络监听
void dispose() {
_subscription.cancel();
}
}
final networkMonitor = NetworkMonitor();
2. 动态超时控制:根据网络状态调整超时时间
通过拦截器动态设置请求超时时间:WiFi环境超时时间短(如10秒),移动网络超时时间中等(如20秒),弱网环境超时时间长(如30秒)。
// dynamic_timeout_interceptor.dart
import 'package:dio/dio.dart';
import 'network_monitor.dart';
// 动态超时拦截器
class DynamicTimeoutInterceptor extends Interceptor {
// 不同网络状态的超时时间(毫秒)
final Map<NetworkState, int> _timeoutMap = {
NetworkState.wifi: 10000,
NetworkState.mobile: 20000,
NetworkState.weakMobile: 30000,
NetworkState.disconnected: 5000, // 无网时快速超时,避免阻塞
NetworkState.unknown: 15000,
};
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 根据当前网络状态设置超时时间
final timeout = _timeoutMap[networkMonitor.currentState] ?? 15000;
options.connectTimeout = Duration(milliseconds: timeout);
options.receiveTimeout = Duration(milliseconds: timeout);
options.sendTimeout = Duration(milliseconds: timeout);
handler.next(options);
}
}
// 在NetworkUtil的_initDio中添加动态超时拦截器
void _initDio() {
_dio = Dio();
// 初始化网络监听
networkMonitor.init();
// 添加动态超时拦截器(优先添加,确保超时时间先设置)
_dio.interceptors.add(DynamicTimeoutInterceptor());
// ... 其他拦截器(缓存、日志、签名等)
}
3. 智能重试:基于网络状态与错误类型的重试策略
避免无差别重试(如断网时重试多次),仅对“可重试错误”(如超时、网络波动)进行重试,且根据网络状态调整重试间隔(弱网间隔长,WiFi间隔短)。
// smart_retry_interceptor.dart
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'network_monitor.dart';
// 智能重试拦截器
class SmartRetryInterceptor extends Interceptor {
// 最大重试次数
final int maxRetries;
// 不同网络状态的重试间隔(毫秒)
final Map<NetworkState, int> _retryDelayMap = {
NetworkState.wifi: 1000,
NetworkState.mobile: 2000,
NetworkState.weakMobile: 3000,
NetworkState.disconnected: 0, // 无网时不重试
NetworkState.unknown: 1500,
};
SmartRetryInterceptor({this.maxRetries = 3});
@override
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
// 1. 检查是否已超过最大重试次数
final retryCount = err.requestOptions.extra['retryCount'] as int? ?? 0;
if (retryCount >= maxRetries) {
handler.next(err);
return;
}
// 2. 检查当前网络状态(无网时不重试)
final networkState = networkMonitor.currentState;
if (networkState == NetworkState.disconnected) {
handler.next(err);
return;
}
// 3. 检查是否为可重试错误(超时、网络错误、503服务不可用等)
if (!_isRetryableError(err)) {
handler.next(err);
return;
}
// 4. 增加重试次数
err.requestOptions.extra['retryCount'] = retryCount + 1;
// 5. 根据网络状态获取重试间隔
final delay = _retryDelayMap[networkState] ?? 1500;
if (delay > 0) {
await Future.delayed(Duration(milliseconds: delay));
}
if (kDebugMode) {
print('请求重试:${err.requestOptions.path},重试次数:${retryCount + 1},间隔:$delay ms');
}
// 6. 重新发起请求
try {
final response = await _retryRequest(err.requestOptions);
handler.resolve(response);
} catch (e) {
handler.next(err);
}
}
// 判断是否为可重试错误
bool _isRetryableError(DioException err) {
switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
case DioExceptionType.connectionError:
return true;
case DioExceptionType.response:
// 500系列服务器错误(除500内部服务器错误,部分500错误不可重试)
final statusCode = err.response?.statusCode;
return statusCode == 503 || statusCode == 504;
default:
return false;
}
}
// 重新发起请求
Future<Response<dynamic>> _retryRequest(RequestOptions options) async {
final dio = Dio();
// 复制原始请求的配置
dio.options = options;
// 重新发起请求
return dio.request(
options.path,
data: options.data,
queryParameters: options.queryParameters,
options: options,
);
}
}
// 在NetworkUtil的_initDio中添加智能重试拦截器
void _initDio() {
_dio = Dio();
// ... 其他初始化逻辑(网络监听、动态超时拦截器)
// 添加智能重试拦截器(在错误拦截器前添加)
_dio.interceptors.add(SmartRetryInterceptor(maxRetries: 3));
// ... 其他拦截器(缓存、日志、签名等)
}
四、离线交互适配:网络状态感知与用户体验优化
离线状态下,除了提供缓存数据访问,还需要优化用户交互体验(如禁用需要网络的按钮、显示离线提示、网络恢复后自动同步数据)。
1. 全局网络状态监听与UI适配
通过InheritedWidget或Provider实现网络状态的全局共享,让所有页面都能感知网络状态变化并调整UI。
// network_state_provider.dart
import 'package:flutter/material.dart';
import 'network_monitor.dart';
class NetworkStateProvider extends InheritedWidget {
final NetworkMonitor networkMonitor;
final Widget child;
const NetworkStateProvider({
super.key,
required this.networkMonitor,
required this.child,
});
static NetworkStateProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<NetworkStateProvider>();
}
@override
bool updateShouldNotify(NetworkStateProvider oldWidget) {
// 网络状态变化时通知子组件刷新
return oldWidget.networkMonitor.currentState != networkMonitor.currentState;
}
}
// 在main.dart中包裹根组件
void main() async {
// ... 其他初始化逻辑(Hive、缓存等)
networkMonitor.init();
runApp(
NetworkStateProvider(
networkMonitor: networkMonitor,
child: const MyApp(),
),
);
}
// 示例:使用网络状态的页面
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late NetworkMonitor _networkMonitor;
late StreamSubscription<void> _networkSubscription;
@override
void initState() {
super.initState();
_networkMonitor = NetworkStateProvider.of(context)!.networkMonitor;
// 监听网络状态变化,刷新UI
_networkSubscription = _networkMonitor._connectivity.onConnectivityChanged.listen((_) {
setState(() {});
});
}
@override
void dispose() {
_networkSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final networkState = _networkMonitor.currentState;
return Scaffold(
appBar: AppBar(
title: const Text('离线交互示例'),
actions: [
// 根据网络状态显示离线/在线图标
Icon(
networkState == NetworkState.disconnected ? Icons.wifi_off : Icons.wifi,
color: networkState == NetworkState.disconnected ? Colors.red : Colors.green,
),
const SizedBox(width: 16),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 离线提示
if (networkState == NetworkState.disconnected)
Container(
padding: const EdgeInsets.symmetric(vertical: 8),
width: double.infinity,
color: Colors.yellow[200],
child: const Text(
'当前无网络,展示离线缓存数据',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.orange),
),
),
const SizedBox(height: 16),
// 根据网络状态禁用/启用按钮
ElevatedButton(
onPressed: networkState == NetworkState.disconnected
? null // 离线时禁用
: () async {
// 发起网络请求
try {
final data = await netUtil.getWithCache('home/banner');
// 更新UI
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('请求失败:${(e as DioException).message}')),
);
}
},
child: const Text('刷新轮播图'),
),
const SizedBox(height: 16),
// 显示缓存数据
Expanded(
child: FutureBuilder(
future: netUtil.getWithCache('home/banner', cacheStrategy: CacheStrategy.persistent),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('加载失败:${snapshot.error}'));
}
final data = snapshot.data as List?;
if (data == null || data.isEmpty) {
return const Center(child: Text('无数据'));
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
final banner = data[index];
return ListTile(title: Text(banner['title']));
},
);
},
),
),
],
),
),
);
}
}
2. 离线操作队列:网络恢复后自动同步
对于离线状态下用户发起的需要网络的操作(如提交表单、发送消息),将其加入离线操作队列,网络恢复后自动同步。
// offline_operation_queue.dart
import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter/foundation.dart';
import 'network_monitor.dart';
// 离线操作类型枚举
enum OfflineOperationType {
submitForm,
sendMessage,
updateUserInfo,
}
// 离线操作模型
class OfflineOperation extends HiveObject {
final String operationId; // 操作唯一ID
final OfflineOperationType type; // 操作类型
final String path; // 请求路径
final String method; // 请求方法
final dynamic data; // 请求数据
final int createTime; // 创建时间(时间戳)
OfflineOperation({
required this.operationId,
required this.type,
required this.path,
required this.method,
required this.data,
required this.createTime,
});
Map<String, dynamic> toMap() {
return {
'operationId': operationId,
'type': type.index,
'path': path,
'method': method,
'data': data,
'createTime': createTime,
};
}
factory OfflineOperation.fromMap(Map<String, dynamic> map) {
return OfflineOperation(
operationId: map['operationId'] as String,
type: OfflineOperationType.values[map['type'] as int],
path: map['path'] as String,
method: map['method'] as String,
data: map['data'],
createTime: map['createTime'] as int,
);
}
}
// 注册适配器
class OfflineOperationAdapter extends TypeAdapter<OfflineOperation> {
@override
final typeId = 2;
@override
OfflineOperation read(BinaryReader reader) {
final map = reader.readMap();
return OfflineOperation.fromMap(map);
}
@override
void write(BinaryWriter writer, OfflineOperation obj) {
writer.writeMap(obj.toMap());
}
}
// 离线操作队列管理器(单例)
class OfflineOperationQueueManager {
static final OfflineOperationQueueManager _instance = OfflineOperationQueueManager._internal();
factory OfflineOperationQueueManager() => _instance;
OfflineOperationQueueManager._internal();
late Box<OfflineOperation> _operationBox;
bool _isSyncing = false; // 是否正在同步
// 初始化(注册适配器+打开盒子)
Future<void> init() async {
Hive.registerAdapter(OfflineOperationAdapter());
_operationBox = await Hive.openBox<OfflineOperation>('offline_operation_box');
// 监听网络状态变化,网络恢复后自动同步
_listenNetworkState();
}
// 监听网络状态
void _listenNetworkState() {
networkMonitor._connectivity.onConnectivityChanged.listen((result) {
final currentState = networkMonitor.currentState;
if (currentState != NetworkState.disconnected) {
// 网络恢复,触发同步
syncOfflineOperations();
}
});
}
// 添加离线操作
void addOperation({
required OfflineOperationType type,
required String path,
required String method,
required dynamic data,
}) {
final operation = OfflineOperation(
operationId: DateTime.now().millisecondsSinceEpoch.toString(),
type: type,
path: path,
method: method,
data: json.encode(data), // 转换为JSON字符串存储
createTime: DateTime.now().millisecondsSinceEpoch,
);
_operationBox.add(operation);
if (kDebugMode) {
print('添加离线操作:${operation.operationId}');
}
}
// 同步离线操作
Future<void> syncOfflineOperations() async {
if (_isSyncing || _operationBox.isEmpty) return;
_isSyncing = true;
try {
// 获取所有离线操作(按创建时间排序)
final operations = _operationBox.values.toList()
..sort((a, b) => a.createTime.compareTo(b.createTime));
for (final operation in operations) {
try {
// 发起网络请求同步操作
final dio = Dio();
final data = json.decode(operation.data);
late Response response;
switch (operation.method.toUpperCase()) {
case 'POST':
response = await dio.post(operation.path, data: data);
break;
case 'PUT':
response = await dio.put(operation.path, data: data);
break;
default:
throw Exception('不支持的操作方法:${operation.method}');
}
// 同步成功,删除操作
if (response.statusCode == 200) {
operation.delete();
if (kDebugMode) {
print('同步离线操作成功:${operation.operationId}');
}
}
} catch (e) {
if (kDebugMode) {
print('同步离线操作失败:${operation.operationId},错误:$e');
}
// 单个操作失败,继续同步下一个
continue;
}
}
} catch (e) {
if (kDebugMode) {
print('同步离线操作队列失败:$e');
}
} finally {
_isSyncing = false;
}
}
// 获取所有离线操作
List<OfflineOperation> getOfflineOperations() {
return _operationBox.values.toList();
}
// 清除所有离线操作
void clearAllOperations() {
_operationBox.clear();
}
}
final offlineQueueManager = OfflineOperationQueueManager();
3. 离线操作使用示例
// 提交表单(支持离线)
Future<void> submitForm(Map<String, dynamic> formData) async {
final networkState = networkMonitor.currentState;
if (networkState == NetworkState.disconnected) {
// 离线状态,加入离线操作队列
offlineQueueManager.addOperation(
type: OfflineOperationType.submitForm,
path: 'form/submit',
method: 'POST',
data: formData,
);
// 提示用户
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已加入离线队列,网络恢复后自动提交')),
);
return;
}
// 在线状态,直接发起请求
try {
await netUtil.post('form/submit', data: formData);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('提交成功')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('提交失败:${(e as DioException).message}')),
);
}
}
五、结语:构建“离线可用、弱网流畅”的网络交互体系
Flutter应用的离线缓存与弱网适配,核心是“提前准备(缓存)+ 智能适配(弱网)+ 友好交互(离线)”。本文从分层缓存架构、持久化缓存实现,到动态超时控制、智能重试,再到网络状态感知与离线操作同步,形成了覆盖“数据存储-请求优化-用户交互”的全链路实战方案。
实际开发中,需结合业务场景灵活调整策略:对于实时性要求高的业务(如支付),需严格控制缓存有效期,避免缓存过期;对于离线高频操作(如表单提交),需确保离线操作队列的可靠性与同步的原子性;对于弱网场景,需平衡重试次数与用户等待时间,避免过度重试导致的资源浪费。通过本文的实战技巧,开发者可有效解决网络环境不确定性带来的用户体验问题,提升应用的稳定性与易用性。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐




所有评论(0)