KoderKoder.ai
价格企业教育投资人
登录开始使用

产品

价格企业投资人

资源

联系我们支持教育博客

法律信息

隐私政策使用条款安全可接受使用政策举报滥用

社交

LinkedInTwitter
Koder.ai
语言

© 2026 Koder.ai 保留所有权利。

首页›博客›Flutter 缓存策略:本地缓存、陈旧数据与刷新规则
2025年11月04日·1 分钟

Flutter 缓存策略:本地缓存、陈旧数据与刷新规则

Flutter 缓存策略:本地缓存、陈旧数据与刷新规则——该缓存什么、何时失效、如何在导航间保持一致。

Flutter 缓存策略:本地缓存、陈旧数据与刷新规则

为什么在 Flutter 应用中缓存会变得棘手

在移动应用里,缓存就是把数据的副本放在近处(内存或设备上),这样下一个屏幕能够立即渲染,而不必等待网络。那些数据可能是一组条目、用户资料或搜索结果。

麻烦在于,缓存的数据常常有些不准确。用户会很快察觉:价格没更新、徽章数量感觉卡住了,或者在刚修改后详情页还显示旧信息。调试之所以困难,是因为时机。相同的接口在手动下拉刷新后看起来没问题,但在返回导航、应用恢复或切换账号后可能就出错了。

这里存在真实的权衡。如果你总是获取最新数据,界面会显得缓慢、抖动,并且浪费电量和流量。如果你过度缓存,应用看起来很快,但用户会不再信任所见的内容。

一个简单的目标会很有帮助:让新鲜度可预测。为每个屏幕决定它允许显示的内容(新鲜、可接受的陈旧或离线),数据可以保留多长时间再刷新,以及哪些事件必须使其失效。

想象一个常见流程:用户打开一个订单,然后返回订单列表。如果列表来自缓存,它可能仍显示旧的状态。如果你每次都刷新,列表可能会闪烁并感觉慢。清晰的规则比如“立即显示缓存,后台刷新,响应到达时更新两个屏幕”会让导航间的体验保持一致。

一种简单的缓存与新鲜度思考方式

缓存不仅仅是“存下数据”。它是数据副本加上一条规则:何时这份副本仍然有效。如果只存载荷而不存规则,你会得到两种现实:一个屏幕显示新信息,另一个显示昨天的数据。

一个实用模型是把每个缓存项放到三种状态之一:

  • 新鲜:可以放心显示。
  • 可用但已陈旧:现在展示,后台刷新。
  • 必须刷新:不要信任,展示前先获取(或显示加载状态)。

这样的框架让 UI 可预测,因为每次看到某个状态时都能以相同方式响应。

新鲜度规则应基于你能向队友解释的信号。常见选择有基于时间的过期(比如 5 分钟)、版本变更(schema 或应用版本)、用户操作(下拉刷新、提交、删除)或服务器提示(ETag、last-updated 时间戳或显式的“清除缓存”响应)。

举例:个人资料页立即加载缓存的用户数据。如果是可用但已陈旧,它会显示缓存的名字和头像,然后静默刷新。如果用户刚编辑过个人资料,那就是必须刷新的时刻。应用应立即更新缓存,以便所有屏幕保持一致。

决定谁来管理这些规则:在大多数应用中,最佳默认是:数据层负责新鲜度和失效,UI 只是做出反应(显示缓存、显示加载、显示错误),后端在可能时给出提示。这能防止每个屏幕各自发明规则。

该缓存什么(以及不该缓存什么)

良好缓存从一个问题开始:如果这些数据有点旧,会伤害用户吗?如果答案是“应该没问题”,通常就是本地缓存的好候选。

被频繁读取且变化缓慢的数据通常值得缓存:用户经常滚动的 feed 和列表、目录类内容(产品、文章、模板)以及参考数据如分类或国家。设置与偏好也适合这里,基础的个人信息如名字和头像 URL 也一样。

有风险的是与金钱或时间相关的任何东西。余额、支付状态、库存可用性、预约时段、配送 ETA 和“最后在线”会在陈旧时造成实际问题。可以为速度缓存这些数据,但把缓存当作临时占位符,并在决策点强制刷新(例如,在确认订单前)。

派生的 UI 状态是另一类。保存所选标签、过滤器、搜索查询、排序或滚动位置可以让导航更流畅,但也会在旧选择意外恢复时让用户困惑。一个简单规则:在用户停留在当前流程时把 UI 状态保存在内存中,但当用户有意“重新开始”(比如回到首页)时重置它。

避免缓存会带来安全或隐私风险的数据:秘密(密码、API 密钥)、一次性令牌(OTP、重置密码令牌)以及敏感个人数据,除非你确实需要离线访问。永远不要缓存完整的卡信息或任何增加欺诈风险的数据。

在购物应用中,缓存产品列表能带来很大收益。但结算页应该在购买前总是刷新总价和库存可用性。

选择本地缓存层:内存、磁盘、数据库

大多数 Flutter 应用最终需要本地缓存,这样屏幕加载更快,不会在网络唤醒时一闪而空。关键是决定缓存存放在哪里,因为每一层在速度、容量限制和清理行为上不同。

内存缓存最快。适合刚获取并将在应用打开期间重复使用的数据,比如当前用户资料、最近搜索结果或用户刚查看的产品。代价也很明显:当应用被杀死时它会消失,因此无法帮助冷启动或离线使用。

磁盘键值存储适合你希望跨重启保留的小项。考虑偏好和简单的二进制块:功能开关、“上次选择的标签”和很少变动的小 JSON 响应。把它保持有意为小项。如果你把大列表塞进键值存储,更新会变得混乱,膨胀也很容易发生。

当数据较大、有结构或需要离线能力时,本地数据库是最佳选择。它也有助于需要查询的场景(“所有未读消息”、“购物车中的商品”、“上个月的订单”),而不是加载一个巨大的 blob 然后在内存中过滤。

为了让缓存可预测,为每种数据选择一个主存储,避免把同一数据集放在三处。

一个快速经验法:

  • 内存:在当前会话内重用
  • 磁盘键值:小、简单、不常变
  • 数据库:大、结构化、支持离线和查询

还要为大小做计划。决定什么叫“太大”,数据保留多久,以及如何清理。例如:限制缓存搜索结果为最近 20 条查询,定期删除 30 天前的记录,防止缓存悄然无限增长。

对用户可接受的陈旧数据的刷新规则

刷新规则应足够简单,以便能用一句话说明每个屏幕的行为。这正是合理缓存的好处:用户得到快速的屏幕,应用仍然值得信赖。

最简单的规则是 TTL(生存时间)。把数据与时间戳一起存储,并把它视作在例如 5 分钟内新鲜。之后它变为陈旧。TTL 适合“锦上添花”的数据,如 feed、分类或推荐。

一个有用的改进是把 TTL 分为软 TTL 和硬 TTL。

在软 TTL 下,你立即显示缓存数据,然后后台刷新并在变更时更新 UI。在硬 TTL 下,一旦过期就不再显示旧数据。你要么用加载器阻塞,要么显示“离线/请重试”状态。硬 TTL 适合错误比缓慢更糟糕的场景,比如余额、订单状态或权限。

如果后端支持,优先使用“只有在变更时才刷新”的方式,利用 ETag、updatedAt 或版本字段。应用可以询问“有没有变化?”,如果没有则跳过下载完整载荷。

对许多屏幕来说,用户友好的默认是 stale-while-revalidate:先显示,静默刷新,仅在结果不同的时候重绘。它带来了速度而减少随机闪烁。

每个屏幕的新鲜度通常会像这样:

  • 主页信息流:软 TTL(1 到 5 分钟),打开时后台刷新
  • 个人资料:软 TTL(15 到 60 分钟),编辑后刷新
  • 消息:硬 TTL 或非常短的 TTL(0 到 10 秒),恢复时刷新
  • 结算/订单:硬 TTL,总是与服务器确认
  • 设置/静态列表:长 TTL(天),在应用更新时刷新

基于出错代价而不是仅仅请求代价来选择规则。

什么时候使缓存失效(关键触发器)

通过对话构建你的数据层
描述你的仓库和缓存状态,然后让 Koder.ai 生成 Flutter 结构。
开始构建

缓存失效始于一个问题:哪个事件会让缓存变得不如重新请求的代价值得信任?如果你选择一小套触发器并坚持使用,行为就会可预测,UI 也会显得稳定。

现实应用中最重要的触发器:

  • 用户驱动:下拉刷新、在错误后点重试,或返回用户期望“最新”的屏幕(如收件箱)。
  • 生命周期:应用启动(预热缓存,然后刷新)和从后台恢复(仅当缓存比 TTL 旧时刷新)。
  • 身份:登录、登出、切换账户。把它当作硬边界:清除用户范围缓存并取消与旧用户相关的进行中请求。
  • 写操作(CRUD):创建、更新或删除后,立即更新缓存以保持界面一致。仅在服务器可能应用额外规则时再次 refetch(例如排序、权限、计算字段)。
  • 安全:应用升级、本地 schema 变更、功能开关变化。增加缓存版本并清空或迁移。

举例:用户编辑了头像然后返回。如果你只依赖基于时间的刷新,之前的屏幕可能会继续显示旧图片直到下一次抓取。相反,应把编辑视为触发器:立即更新缓存的 profile 对象并用新时间戳标记为新鲜。

保持失效规则简洁明确。如果你无法指出确切事件去使缓存失效,你会么么刷新太频繁(界面慢、闪烁),要么刷新不够(页面陈旧)。

逐步实现可预测的缓存流程

先列出关键屏幕和每个屏幕需要的数据。不要以端点为思路,要以用户可见对象为中心:个人资料、购物车、订单列表、目录条目、未读计数。

接着,为每种数据选择一个事实来源。在 Flutter 中,这通常是一个 repository,隐藏数据来自何处(内存、磁盘或网络)。屏幕不应决定何时访问网络。它们应向 repository 请求数据并响应返回的状态。

一个实用流程:

  • 将屏幕映射到所需数据(以及它是否为用户作用域或共享)。
  • 所有读写都通过每种数据类型的一个 repository 路径。
  • 保存数据时附带元数据:savedAt 时间戳、schema/app 版本和 ownerUserId。
  • 定义 UI 对四种状态的行为:新鲜、陈旧、加载、错误。
  • 添加两条刷新路径:手动(下拉刷新)和后台(陈旧时静默刷新)。

元数据使规则可执行。如果 ownerUserId 变化(登出/登录),你可以立即丢弃或忽略旧的缓存行,而不是短暂显示前一个用户的数据。

对于 UI 行为,事先决定“陈旧”意味着什么。一个常见规则:立即展示陈旧数据以避免空白屏,触发后台刷新,到达后更新界面。如果刷新失败,则保留陈旧数据并显示一个小而清晰的错误。

然后用一些无聊但重要的测试锁定规则:

  • TTL:数据在 X 分钟后变为陈旧。
  • 登出:与 ownerUserId 关联的缓存被清除或隔离。
  • 更新:本地写入立即更新缓存,然后同步。
  • 版本提升:schema/app 版本变更后忽略旧缓存。
  • 错误:刷新失败时保留陈旧数据可见。

这就是“我们有缓存”与“我们的应用每次表现一致”之间的差别。

在导航间保持屏幕一致性

先规划缓存规则
在写一行代码之前,先在 Planning Mode 草拟每个屏幕的时效规则。
免费试用

没有什么比在列表页看到一个值、点进详情修改它、再返回却看到旧值更能破坏信任。导航一致性来自于让每个屏幕都从同一来源读取。

一个稳健规则是:只取一次,存一次,多处渲染。屏幕不应独立调用同一端点然后各自保留私有副本。把缓存数据放在共享存储(你的状态管理层),让列表页和详情页都监听同一数据。

建立唯一的事实来源

保留一个拥有当前值与新鲜度的单一位置。屏幕可以请求刷新,但不应分别管理它们的定时器、重试和解析。

避免“两个现实”的实用习惯:

  • 在路由间共享状态(每个 feature 用一个 store/provider,而不是每个 widget 都有一份)。
  • 去重正在进行的请求,避免两个屏幕同时重复抓取同一数据。
  • 在返回导航时从 store 重新读取,而不是复活旧的 widget 状态。
  • 编辑后立即更新 store,让每个屏幕都反映更改。

呈现正在发生的事

即便规则很好,用户有时仍会看到陈旧数据(离线、网速慢、后台恢复)。用小而冷静的提示让它变得明显:一个“刚更新”时间戳、细微的“正在刷新…”指示或“离线”徽章。

对于编辑,乐观更新通常体验最好。举例:用户在详情页改了商品价格。立即更新共享 store,这样返回列表时就能看到新价格。如果保存失败,回滚到以前的值并显示短消息。

常见缓存错误与避免方法

大多数缓存失败都很平凡:缓存工作了,但没人能解释何时该用、何时过期、谁来负责。

第一个陷阱是没有元数据的缓存。如果你只保存载荷,就无法判断它是否过旧、哪个应用版本产生它或它属于哪个用户。至少保存 savedAt、一个简单的版本号和 userId(或租户键)。这个习惯能避免很多“为什么这个屏幕错了?”的 bug。

另一个常见问题是为同一数据维护多个缓存且无人所有。列表页保留内存列表,仓库写入磁盘,详情页再次抓取并保存到别处。选择一个事实来源(通常是 repository 层),让每个屏幕都通过它读取。

账户切换也是常见坑。如果有人登出或切换账户,清除用户范围的表和键。否则你可能短暂显示前一个用户的头像或订单,这会像隐私泄露。

覆盖上述问题的实用修复:

  • 把缓存条目与 savedAt、版本和 userId 一起存储,而不仅仅是 JSON。
  • 为每个数据集定义一个所有者(例如 ProfileRepository 拥有 profile)。
  • 对大多数屏幕使用“立即展示缓存,后台刷新”的规则。
  • 对刷新做速率限制(例如每个端点每 X 分钟只允许一次网络刷新)。
  • 将刷新错误展示出来(小横幅或重试)并设定最大陈旧时间。

举例:产品列表即时从缓存加载,然后静默刷新。如果刷新失败,继续显示缓存并明确提示数据可能过时,同时提供重试。不要在缓存可用时阻塞 UI。

发版前的快速检查清单

在发布前,把缓存从“看起来行得通”变为可测试的规则。即便用户来回导航、离线或用不同账户登录,他们也应该看到合理的数据。

定义新鲜度与刷新行为

为每个屏幕决定数据可被认为新鲜的时长。快速变化的数据(消息、余额)可能是几分钟,而缓慢变更的数据(设置、产品分类)可能是几小时。然后确认当数据不新鲜时会发生什么:后台刷新、打开时刷新或手动下拉刷新。

锁定失效与缓存元数据

为每种数据决定哪些事件必须清除或绕过缓存。常见触发器包括登出、编辑项、切换账户以及改变数据形状的应用更新。

确保在载荷旁存储一小组元数据:

  • savedAt
  • userId
  • 版本(schema/app 版本)
  • 来源(可选:network 或 local)

把所有权弄清楚:对每种数据使用一个 repository(例如 ProductsRepository),而不是每个 widget 一个。Widget 应该请求数据,而不是决定缓存规则。

还要决定并测试离线行为。确认从缓存显示哪些屏幕、哪些操作被禁用以及显示什么文案(“显示已保存的数据”,并带明显的刷新控件)。每个基于缓存的屏幕都应有手动刷新,且要容易找到。

示例:保持一致性的目录应用

安全调整缓存策略
在调整 TTL 和失效规则时进行实验,如果出现闪烁可回滚更改。
使用快照

想象一个简单的商店应用,包含三屏:产品目录(列表)、产品详情和收藏页。用户滚动目录,打开产品,并点击爱心图标收藏。目标是在慢网络下也感觉迅速,同时不显示令人困惑的不一致。

本地缓存能帮你立即渲染的信息:目录页的条目(ID、标题、价格、缩略图 URL、收藏标记)、产品详情(描述、规格、可用性、lastUpdated)、图片元数据(URL、尺寸、cache keys)和用户的收藏集合(产品 ID 集合,可选带时间戳)。

当用户打开目录时,立即显示缓存结果,然后后台重新验证。如果有新鲜数据到达,仅更新变化的部分并保持滚动位置。

对于收藏切换,把它视为“必须一致”的操作。乐观更新本地收藏集合,立刻更新缓存的产品行和该 ID 的产品详情缓存。如果网络调用失败,则回滚并显示简短提示。

为保持导航一致性,让列表徽章和详情心形图标都来自相同的事实来源(本地缓存或 store),而不是来自不同的屏幕状态。返回时列表心形会立即更新,详情页会反映从列表发起的更改,收藏页的计数在各处一致而无需等待重抓。

添加简单的刷新规则:目录缓存过期快(分钟级),产品详情稍长,收藏永不过期但在登录/登出后要进行对账。

下一步:把规则写下来并便于维护

当团队能指向一页规则并就应该发生的事情达成一致时,缓存就不再神秘。目标不是完美,而是可预测的行为在各个版本中保持一致。

为每个屏幕写一张小表并保持简短,便于在变更时审查:屏幕名称与主要数据、缓存位置与键、新鲜度规则(TTL、基于事件或手动)、失效触发器以及刷新期间用户看到的内容。

在调整时添加轻量级日志。记录缓存命中、未命中以及为什么发生刷新(TTL 到期、用户下拉刷新、应用恢复、变更完成)。当有人反馈“这个列表看起来不对”时,这些日志能让问题可定位。

从简单的 TTL 开始,然后根据用户注意到的问题进行细化。新闻信息流可能允许 5 到 10 分钟的陈旧,而订单状态屏幕可能需要在恢复时刷新并在任何结账操作后刷新。

如果你在快速构建 Flutter 应用,先在规划阶段概述数据层和缓存规则会很有帮助。对于使用 Koder.ai 的团队,Planning Mode 是把每个屏幕规则写好然后按规则实现的实用场所。

在调整刷新行为时,保护稳定的屏幕以便实验。快照与回滚能在新规则意外引入闪烁、空状态或导航间计数不一致时节省时间。

常见问题

让缓存在 Flutter 应用中变得可预测最简单的方法是什么?

从为每个屏幕设定一个明确规则开始:它可以立即展示什么(缓存),什么时候必须刷新,以及刷新期间用户看到什么。如果你无法用一句话解释清楚该规则,应用最终会显得不一致。

如何判断缓存数据是新鲜、已陈旧还是不可用?

把缓存数据看作有新鲜度状态。如果它是 新鲜,就直接展示。如果是 可用但已陈旧,现在展示并在后台静默刷新。如果是 必须刷新,在展示之前先请求新数据(或展示加载/离线状态)。这样 UI 行为就不是“有时更新,有时不更新”。

我应该缓存什么数据,哪些不该缓存?

缓存那些被频繁读取且即使有点陈旧也不会伤害用户的数据,如信息流、目录、参考数据和基本个人信息。谨慎对待与金钱或时效相关的数据(余额、库存、ETA、订单状态);可以为速度缓存它们,但在关键决策或确认前强制刷新。

我的缓存应该存在内存、磁盘还是本地数据库?

会话内重用时用内存(当前配置文件、最近查看项);需要跨重启保存的简单小项用磁盘键值存储(偏好设置、小 JSON);当数据大、结构化或需离线与查询时,用本地数据库(消息、订单、库存)。

TTL(生存时间)规则对大多数屏幕够吗?

单纯的 TTL 是一个不错的默认:把数据设为一段时间内新鲜,然后刷新。不过更好的体验是“先显示缓存,后台刷新,若有变化再更新”,因为这样能避免空白屏和频繁闪烁。

哪些事件应该触发缓存失效?

在明确降低缓存可信度的事件发生时失效:用户编辑(创建/更新/删除)、登录/登出或切换账户、从后台恢复且数据超过 TTL、以及用户显式刷新。保持触发器简洁明确,避免过度或不足的刷新。

编辑详情后返回列表,如何避免列表显示旧数据?

让两个屏幕都读同一个事实来源,而不是各自维护私有副本。当用户在详情页编辑后,立即更新共享缓存对象,这样返回列表时会渲染新值,然后再与服务器同步,失败时回滚。

如何防止缓存数据在登出或切换账户后泄漏到其他用户?

务必在有效载荷旁保存元数据,尤其是时间戳和用户标识。登出或切换账户时立即清除或隔离用户范围的缓存项,并取消与旧用户相关的进行中的请求,这样就不会短暂地呈现前一个用户的数据。

后台刷新失败时 UI 应该怎么做?

默认保留陈旧数据可见,并展示一个小且清晰的错误提示并提供重试,而不是把屏幕置空。如果屏幕不能安全显示旧数据,就改用必须刷新的规则并显示加载或离线信息,而不是冒然展示不可靠的数值。

缓存逻辑应该放在组件、状态管理还是数据层?

把缓存规则放在数据层(例如仓库)中,让每个屏幕遵循相同行为。如果你在 Koder.ai 中快速构建,先在 Planning Mode 把每个屏幕的新鲜度与失效规则写好,再实现,这样 UI 只需对状态做出反应,而不是自己发明刷新逻辑。

目录
为什么在 Flutter 应用中缓存会变得棘手一种简单的缓存与新鲜度思考方式该缓存什么(以及不该缓存什么)选择本地缓存层:内存、磁盘、数据库对用户可接受的陈旧数据的刷新规则什么时候使缓存失效(关键触发器)逐步实现可预测的缓存流程在导航间保持屏幕一致性常见缓存错误与避免方法发版前的快速检查清单示例:保持一致性的目录应用下一步:把规则写下来并便于维护常见问题
分享
Koder.ai
使用 Koder 构建您自己的应用 立即!

了解 Koder 强大功能的最佳方式是亲自体验。

免费开始预约演示