本地缓存
现在你已经学会了如何通过网络从服务器加载数据,你的 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 应用程序中缓存数据的多种方法,但要知道所有这些方法都可以根据你的情况进行调整或组合。
在本地内存中缓存数据
#最简单、性能最高的缓存策略是内存缓存。这种策略的缺点是,由于缓存仅存储在系统内存中,因此除了数据最初缓存的会话之外,不会保留任何数据。(当然,这个“缺点”也有一个优点,那就是自动解决了大多数陈旧缓存问题!)
由于其简单性,内存缓存与上面看到的伪代码非常相似。也就是说,最好使用经过验证的设计原则,例如仓库模式,来组织你的代码并防止上述缓存检查出现在你的整个代码库中。
想象一个 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 所有六个目标平台上特定于平台的键值存储。尽管这些底层平台键值存储是为小数据量设计的,但它们仍然适用于大多数应用程序的缓存策略。有关完整指南,请参阅我们关于使用键值存储的其他资源。
使用文件系统缓存数据
#如果您的 Flutter 应用超出了 shared_preferences
理想的低吞吐量场景,您可能已准备好探索使用设备文件系统缓存数据。有关更全面的指南,请参阅我们关于文件系统缓存的其他资源。
- 烹饪书:读写文件
使用设备上的数据库缓存数据
#本地数据缓存的终极解决方案是使用合适的数据库读写数据的任何策略。存在多种类型,包括关系型和非关系型数据库。所有方法都比简单文件显著提高性能——特别是对于大型数据集。有关更全面的指南,请参阅以下资源:
- 烹饪书:使用 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 状态恢复
反馈
#由于本网站的这一部分正在不断发展,我们欢迎您的反馈!