Skip to main content

本地缓存

现在您已经学习了如何从网络服务器加载数据,您的 Flutter 应用应该感觉更生动活泼了。但是,仅仅因为您可以从远程服务器加载数据并不意味着您总是应该这样做。有时,最好重新渲染从之前的网络请求接收到的数据,而不是重复它并让用户等待它再次完成。这种保留应用程序数据以便在将来再次显示的技术称为 缓存 ,本页面介绍如何在 Flutter 应用中处理此任务。

缓存介绍

#

最基本的,所有缓存策略都相当于相同的三个步骤操作,以下伪代码表示:

dart
Data? _cachedData;

Future<Data> get data async {
    // 步骤 1:检查缓存是否已包含所需数据
    if (_cachedData == null) {
        // 步骤 2:如果缓存为空,则加载数据
        _cachedData = await _readData();
    }
    // 步骤 3:返回缓存中的值
    return _cachedData!;
}

有很多有趣的方法可以改变这种策略,包括缓存的位置,您预先写入值或“预热”缓存的程度等等。

常见的缓存术语

#

缓存有其自身的术语,其中一些术语在下面定义和解释。

缓存命中
当缓存中已包含所需信息并且无需从真实数据源加载它时,应用程序据说发生了缓存命中。
缓存未命中
当缓存为空并且所需数据从真实数据源加载,然后保存到缓存以供将来读取时,应用程序据说发生了缓存未命中。

缓存数据的风险

#

当真实数据源中的数据发生更改时,应用程序据说具有 陈旧缓存 ,这使应用程序面临呈现旧的、过时信息的风险。

所有缓存策略都存在持有陈旧数据的风险。不幸的是,验证缓存新鲜度的操作通常需要与完全加载相关数据一样多的时间。这意味着大多数应用程序只有在信任数据在运行时无需验证即可保持新鲜的情况下才能从缓存数据中获益。

为了解决这个问题,大多数缓存系统都会对任何单个缓存数据的片段设置时间限制。超过此时间限制后,潜在的缓存命中将被视为缓存未命中,直到加载新鲜数据为止。

计算机科学家之间的一个流行笑话是:“计算机科学中最难的两件事是缓存失效、命名事物和越界错误。”😄

尽管存在这些风险,但世界上几乎每个应用程序都大量使用数据缓存。本页的其余部分探讨了在 Flutter 应用中缓存数据的多种方法,但请注意,所有这些方法都可以根据您的情况进行调整或组合。

在本地内存中缓存数据

#

最简单、性能最高的缓存策略是内存缓存。这种策略的缺点是,由于缓存仅保存在系统内存中,因此在原始缓存会话之外不会保留任何数据。(当然,这个“缺点”也有一个好处,那就是自动解决了大多数陈旧缓存问题!)

由于其简单性,内存缓存与上面看到的伪代码非常相似。也就是说,最好使用经过验证的设计原则,例如存储库模式,来组织代码并防止上述缓存检查出现在整个代码库中。

想象一下一个 UserRepository 类,它也负责在内存中缓存用户以避免重复的网络请求。它的实现可能如下所示:

dart
class UserRepository {
  UserRepository(this.api);
  
  final Api api;
  final Map<int, User?> _userCache = {};

  Future<User?> loadUser(int id) async {
    if (!_userCache.containsKey(id)) {
      final response = await api.get(id);
      if (response.statusCode == 200) {
        _userCache[id] = User.fromJson(response.body);
      } else {
        _userCache[id] = null;
      }
    }
    return _userCache[id];
  }
}

UserRepository 遵循多个经过验证的设计原则,包括:

  • 依赖注入,这有助于测试
  • 松耦合,这可以保护周围的代码免受其实现细节的影响,以及
  • 关注点分离,这可以防止其实现处理过多的问题。

最重要的是,无论用户在单个会话中访问加载给定用户的 Flutter 应用页面多少次,UserRepository 类都只加载一次网络上的数据。

但是,您的用户最终可能会厌倦每次重新启动应用程序时等待数据加载。为此,您应该从下面找到的持久性缓存策略中选择一种。

持久性缓存

#

在内存中缓存数据永远不会让您宝贵的缓存超出用户单个会话的生存期。要享受在重新启动应用程序时缓存命中的性能优势,您需要将数据缓存到设备的硬盘驱动器上的某个位置。

使用 shared_preferences 缓存数据

#

shared_preferences 是一个 Flutter 插件,它在 Flutter 的所有六个目标平台上包装了特定于平台的键值存储。尽管这些底层平台键值存储是为小型数据量设计的,但它们仍然适合大多数应用程序的缓存策略。有关完整指南,请参阅我们关于使用键值存储的其他资源。

使用文件系统缓存数据

#

如果您的 Flutter 应用超过了 shared_preferences 理想的低吞吐量场景,您可能已准备好探索使用设备的文件系统缓存数据。有关更全面的指南,请参阅我们关于文件系统缓存的其他资源。

使用设备数据库缓存数据

#

本地数据缓存的最终方法是任何使用适当的数据库来读取和写入数据的策略。存在多种类型,包括关系数据库和非关系数据库。所有方法都比简单的文件提供了显著改进的性能——特别是对于大型数据集而言。有关更全面的指南,请参阅以下资源:

缓存图像

#

缓存图像与缓存常规数据类似,尽管有一个一劳永逸的解决方案。要指示 Flutter 应用使用文件系统存储图像,请使用cached_network_image

状态恢复

#

除了应用程序数据外,您可能还需要持久化用户会话的其他方面,例如他们的导航堆栈、滚动位置,甚至填写表单的部分进度。此模式称为“状态恢复”,它是 Flutter 的内置功能。

状态恢复通过指示 Flutter 框架将其 Element 树中的数据与 Flutter 引擎同步来工作,然后将其缓存到特定于平台的存储中以供将来会话使用。要在 Android 和 iOS 上启用 Flutter 的状态恢复,请参阅以下文档:

反馈

#

由于本网站的此部分正在不断发展,我们欢迎您的反馈