跳至主要内容

本地缓存

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

缓存简介

#

从最基本的层面上讲,所有缓存策略都相当于相同的三个步骤操作,以下伪代码表示了这些步骤:

dart
Data? _cachedData;

Future<Data> get data async {
    // Step 1: Check whether your cache already contains the desired data
    if (_cachedData == null) {
        // Step 2: Load the data if the cache was empty
        _cachedData = await _readData();
    }
    // Step 3: Return the value in the cache
    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 引擎同步来工作,然后 Flutter 引擎将这些数据缓存到特定于平台的存储中以供将来会话使用。要在 Flutter 的 Android 和 iOS 上启用状态恢复,请参阅以下文档:

反馈

#

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