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,
  }) =&gt;
      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应用的离线缓存与弱网适配,核心是“提前准备(缓存)+ 智能适配(弱网)+ 友好交互(离线)”。本文从分层缓存架构、持久化缓存实现,到动态超时控制、智能重试,再到网络状态感知与离线操作同步,形成了覆盖“数据存储-请求优化-用户交互”的全链路实战方案。

实际开发中,需结合业务场景灵活调整策略:对于实时性要求高的业务(如支付),需严格控制缓存有效期,避免缓存过期;对于离线高频操作(如表单提交),需确保离线操作队列的可靠性与同步的原子性;对于弱网场景,需平衡重试次数与用户等待时间,避免过度重试导致的资源浪费。通过本文的实战技巧,开发者可有效解决网络环境不确定性带来的用户体验问题,提升应用的稳定性与易用性。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐