本地缓存
现在你已经学会了如何通过网络从服务器加载数据,你的 Flutter 应用应该会感觉更有活力。但是,仅仅因为你可以从远程服务器加载数据,并不意味着你应该总是这样做。有时,最好是重新渲染你从上一次网络请求中接收到的数据,而不是重复请求并让你的用户再次等待其完成。这种保留应用程序数据以便将来再次显示的技术称为缓存,本页面将介绍如何在 Flutter 应用中处理此任务。
缓存简介
#最基本的缓存策略都归结为相同的三个步骤操作,用以下伪代码表示
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 应用中缓存数据的多种方法,但请注意,所有这些方法都可以根据你的具体情况进行调整或组合。
在本地内存中缓存数据
#最简单、性能最高的缓存策略是内存缓存。这种策略的缺点是,由于缓存仅保存在系统内存中,因此除了数据最初缓存的会话之外,不会保留任何数据。(当然,这个“缺点”也有一个优点,那就是自动解决了大多数陈旧缓存问题!)
由于其简单性,内存缓存与上述伪代码非常相似。也就是说,最好使用经过验证的设计原则,例如仓库模式(repository pattern),来组织你的代码并防止上述缓存检查出现在你的代码库中。
假设有一个 UserRepository
类,它还负责在内存中缓存用户以避免重复的网络请求。其实现可能如下所示
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 所有六个目标平台上特定于平台的键值存储。尽管这些底层平台键值存储是为小数据量设计的,但它们仍然适用于大多数应用程序的缓存策略。有关完整指南,请参阅我们关于使用键值存储的其他资源。
- Cookbook: 在磁盘上存储键值数据
- 视频: 本周包:
shared_preferences
使用文件系统缓存数据
#如果你的 Flutter 应用超出了 shared_preferences
理想的低吞吐量场景,你可能需要探索使用设备的 文件系统 缓存数据。如需更详细的指南,请参阅我们关于文件系统缓存的其他资源。
- Cookbook: 读写文件
使用设备数据库缓存数据
#本地数据缓存的终极解决方案是使用适当的数据库来读写数据的任何策略。存在多种类型,包括关系型和非关系型数据库。所有方法都比简单的文件提供了显著的性能提升——特别是对于大型数据集。有关更详细的指南,请参阅以下资源
- Cookbook: 使用 SQLite 持久化数据
- SQLite 替代方案:
sqlite3
包 - Drift,一个关系型数据库:
drift
包 - Hive CE,一个非关系型数据库:
hive_ce
包 - Remote Caching,一个用于 API 响应的轻量级缓存系统:
remote_caching
包
缓存图片
#缓存图片是一个与缓存常规数据类似的问题领域,但它有一个一劳永逸的解决方案。要指导你的 Flutter 应用使用文件系统存储图片,请使用 cached_network_image
包。
状态恢复
#除了应用程序数据,你可能还希望持久化用户会话的其他方面,例如他们的导航堆栈、滚动位置,甚至是填写表单的部分进度。这种模式称为“状态恢复”,并且已内置于 Flutter 中。
状态恢复的工作原理是,指示 Flutter 框架将其元素树中的数据与 Flutter 引擎同步,然后引擎将其缓存到特定于平台的存储中,以供将来会话使用。要在 Android 和 iOS 的 Flutter 上启用状态恢复,请参阅以下文档
- Android 文档: Android 状态恢复
- iOS 文档: iOS 状态恢复
反馈
#由于本网站的这一部分正在不断发展,我们欢迎您的反馈!