3行代码搞定Flutter网络缓存:GetX Connect实战指南

【免费下载链接】getx Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get. 【免费下载链接】getx 项目地址: https://gitcode.com/gh_mirrors/ge/getx

你是否还在为Flutter应用中的重复网络请求烦恼?用户反复刷新页面导致流量浪费?数据加载缓慢影响体验?本文将带你用GetX的GetConnect模块,通过简单几步实现高效的API请求缓存策略,让你的应用瞬间提升响应速度和用户体验。

读完本文你将学到:

  • 如何用GetConnect拦截器实现请求缓存
  • 3种实用的缓存策略(内存缓存、持久化缓存、条件缓存)
  • 缓存过期时间和失效机制的最佳实践
  • 完整的代码示例和项目文件路径参考

GetConnect缓存基础

GetConnect是GetX框架提供的网络请求模块,通过它可以轻松实现REST API调用和缓存管理。核心原理是通过拦截器(Interceptor)在请求发送前和响应返回后进行缓存处理。

项目核心文件

GetConnect的HTTP实现位于lib/get_connect/http/src/http.dart,其中定义了请求和响应的拦截方法:

void addRequestModifier<T>(RequestModifier<T> interceptor) {
  _modifier.addRequestModifier<T>(interceptor);
}

void addResponseModifier<T>(ResponseModifier<T> interceptor) {
  _modifier.addResponseModifier(interceptor);
}

这两个方法允许我们添加自定义的请求和响应拦截器,从而实现缓存逻辑。

实现内存缓存策略

内存缓存是最简单的缓存方式,适用于频繁访问且变化不频繁的数据。下面我们通过一个实际例子来实现。

1. 创建缓存拦截器

首先创建一个缓存管理类,用于存储和获取缓存数据:

class CacheManager {
  final Map<String, CacheItem> _cache = {};

  // 添加数据到缓存
  void addToCache(String key, dynamic data, {Duration maxAge = const Duration(minutes: 5)}) {
    _cache[key] = CacheItem(data, DateTime.now().add(maxAge));
  }

  // 从缓存获取数据
  dynamic getFromCache(String key) {
    final item = _cache[key];
    if (item == null || DateTime.now().isAfter(item.expiryTime)) {
      _cache.remove(key); // 移除过期缓存
      return null;
    }
    return item.data;
  }
}

class CacheItem {
  final dynamic data;
  final DateTime expiryTime;

  CacheItem(this.data, this.expiryTime);
}

2. 在GetConnect中应用缓存

在实际项目中,GetConnect的使用示例可以参考example/lib/pages/home/data/home_api_provider.dart

class HomeProvider extends GetConnect implements IHomeProvider {
  final CacheManager _cacheManager = CacheManager();

  @override
  void onInit() {
    httpClient.baseUrl = API_URL;
    
    // 添加响应拦截器,缓存成功的GET请求
    httpClient.addResponseModifier((request, response) {
      if (request.method == 'GET' && response.statusCode == 200) {
        _cacheManager.addToCache(request.url.toString(), response.body);
      }
      return response;
    });
    
    // 添加请求拦截器,优先从缓存获取数据
    httpClient.addRequestModifier((request) {
      if (request.method == 'GET') {
        final cachedData = _cacheManager.getFromCache(request.url.toString());
        if (cachedData != null) {
          return request.copyWith(
            body: cachedData,
            headers: {...request.headers, 'X-Cache': 'hit'},
          );
        }
      }
      return request;
    });
    
    super.onInit();
  }
  
  // ...其他方法
}

这段代码实现了基本的内存缓存功能:对于GET请求,先检查缓存,如果存在未过期的缓存则直接返回,否则发送网络请求并缓存结果。

持久化缓存实现

内存缓存在应用重启后会丢失,对于需要长期保存的数据,我们需要使用持久化缓存。GetX推荐使用get_storage包来实现本地存储。

添加持久化缓存逻辑

修改上面的CacheManager类,添加持久化支持:

import 'package:get_storage/get_storage.dart';

class CacheManager {
  final Map<String, CacheItem> _memoryCache = {};
  final GetStorage _storage = GetStorage('api_cache');
  
  // 添加数据到缓存(同时保存到内存和本地存储)
  void addToCache(String key, dynamic data, {Duration maxAge = const Duration(minutes: 5)}) {
    final expiryTime = DateTime.now().add(maxAge);
    _memoryCache[key] = CacheItem(data, expiryTime);
    _storage.write(key, {
      'data': data,
      'expiryTime': expiryTime.toIso8601String(),
    });
  }
  
  // 从缓存获取数据(优先内存缓存)
  dynamic getFromCache(String key) {
    // 先检查内存缓存
    final memoryItem = _memoryCache[key];
    if (memoryItem != null && !DateTime.now().isAfter(memoryItem.expiryTime)) {
      return memoryItem.data;
    }
    
    // 内存缓存不存在或过期,检查本地存储
    final storageItem = _storage.read<Map>(key);
    if (storageItem != null) {
      final expiryTime = DateTime.parse(storageItem['expiryTime']);
      if (!DateTime.now().isAfter(expiryTime)) {
        // 本地缓存有效,加载到内存
        _memoryCache[key] = CacheItem(storageItem['data'], expiryTime);
        return storageItem['data'];
      } else {
        // 本地缓存过期,移除
        _storage.remove(key);
      }
    }
    
    return null;
  }
}

条件缓存策略

有时我们需要根据特定条件来决定是否使用缓存,例如根据HTTP响应头中的Cache-Control字段。GetConnect的响应处理代码在lib/get_connect/http/src/response/client_response.dart中提到了对Cache-Control的支持:

static const cacheControlHeader = "cache-control";

我们可以利用这一点实现更智能的缓存策略:

// 在响应拦截器中根据Cache-Control头设置缓存
httpClient.addResponseModifier((request, response) {
  if (request.method == 'GET' && response.statusCode == 200) {
    final cacheControl = response.headers[cacheControlHeader];
    if (cacheControl != null) {
      final maxAge = _parseMaxAge(cacheControl);
      if (maxAge != null) {
        _cacheManager.addToCache(request.url.toString(), response.body, maxAge: maxAge);
      }
    }
  }
  return response;
});

// 解析Cache-Control头中的max-age值
Duration? _parseMaxAge(String cacheControl) {
  final parts = cacheControl.split(',');
  for (var part in parts) {
    part = part.trim();
    if (part.startsWith('max-age=')) {
      final seconds = int.tryParse(part.split('=').last);
      if (seconds != null) {
        return Duration(seconds: seconds);
      }
    }
  }
  return null;
}

完整示例:缓存国家数据API

让我们将上述缓存策略应用到实际的API请求中。以example/lib/pages/home/data/home_api_provider.dart中的国家数据请求为例:

class HomeProvider extends GetConnect implements IHomeProvider {
  final CacheManager _cacheManager = CacheManager();

  @override
  void onInit() {
    httpClient.baseUrl = API_URL;
    
    // 请求拦截器:检查缓存
    httpClient.addRequestModifier((request) async {
      if (request.method == 'GET') {
        final cachedData = _cacheManager.getFromCache(request.url.toString());
        if (cachedData != null) {
          // 返回缓存数据,不发送实际请求
          return request.copyWith(
            headers: {...request.headers, 'X-From-Cache': 'true'},
            body: cachedData,
          );
        }
      }
      return request;
    });
    
    // 响应拦截器:缓存数据
    httpClient.addResponseModifier((request, response) async {
      if (request.method == 'GET' && response.statusCode == 200 && 
          request.headers['X-From-Cache'] != 'true') {
        // 根据Cache-Control头设置缓存时间
        final cacheControl = response.headers[cacheControlHeader];
        Duration maxAge = const Duration(minutes: 5); // 默认缓存5分钟
        
        if (cacheControl != null) {
          final parsedMaxAge = _parseMaxAge(cacheControl);
          if (parsedMaxAge != null) {
            maxAge = parsedMaxAge;
          }
        }
        
        _cacheManager.addToCache(request.url.toString(), response.body, maxAge: maxAge);
      }
      return response;
    });
    
    super.onInit();
  }
  
  @override
  Future<Response<List<CountriesItem>>> getCountries() {
    return get(
      '/countries',
      decoder: (data) =>
          (data as List).map((item) => CountriesItem.fromJson(item)).toList(),
    );
  }
  
  // 其他方法...
}

缓存策略总结

根据不同的业务需求,我们可以选择合适的缓存策略:

缓存类型 实现方式 适用场景 优点 缺点
内存缓存 使用Map存储数据 频繁访问、临时数据 速度快、实现简单 应用重启后丢失
持久化缓存 结合GetStorage 用户配置、离线数据 数据持久化 读取速度较慢
条件缓存 根据HTTP头或业务逻辑 API数据、资源文件 智能控制缓存 实现复杂

最佳实践与注意事项

  1. 缓存键设计:使用完整URL作为缓存键,包含查询参数,确保唯一性
  2. 过期策略:根据数据更新频率设置合理的过期时间,避免数据陈旧
  3. 缓存清理:定期清理过期缓存,避免内存和存储占用过大
  4. 网络状态考虑:结合网络状态判断是否使用缓存,离线时强制使用缓存
  5. 避免缓存POST请求:POST通常用于提交数据,不应缓存

总结

通过GetConnect的拦截器机制,我们可以轻松实现灵活高效的网络缓存策略。无论是简单的内存缓存还是复杂的条件缓存,GetX都提供了简洁的API来满足需求。合理使用缓存可以显著提升应用性能,减少网络请求,改善用户体验。

官方文档中还有更多关于依赖管理和状态管理的内容,可以参考documentation/zh_CN/dependency_management.mddocumentation/zh_CN/state_management.md

希望本文对你理解和应用GetX的网络缓存有所帮助!如果你有任何问题或更好的实践方法,欢迎在评论区留言讨论。

【免费下载链接】getx Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get. 【免费下载链接】getx 项目地址: https://gitcode.com/gh_mirrors/ge/getx

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐