← 跨平台应用开发 · 第 7 篇

给学习页面加入可控的离线能力

为学习页面分别设计公共正文、静态资源和私有请求策略,解释Service Worker生命周期、版本缓存与更新恢复。

学完这一篇,你能做到

实现版本缓存与离线提示,验证首次断网、已缓存阅读、更新等待和私有响应不进入公共缓存。

学习页面的离线能力取决于哪些响应被保存、何时更新以及失败时显示什么。加入 manifest 可以描述应用安装信息,却不会自动保存课程。一个可控的首版应只缓存明确公开的学习内容,并让用户知道本地版本。

案例有公开入门页、课程图片和需要身份校验的进度接口。它们共用网站,但数据性质不同。用同一缓存规则处理全部请求,可能将私人响应混进公共缓存,也可能让 HTML 与脚本来自不兼容版本。

三种存储各有作用

HTTP 缓存由浏览器和响应头参与管理,Cache API 让程序保存请求与响应,IndexedDB 适合结构化本地数据。它们不是同一层。课程响应可以放 Cache API,用户笔记若需要持久编辑,应有独立数据模型,不能靠缓存页面完成。

先列资源类型。带版本指纹的公共脚本适合复用已缓存副本;正文可以优先请求网络,再在失败时使用已保存版本;私人进度接口保持身份校验并采用独立离线队列设计。这里不把含认证内容的响应加入公共离线缓存。

缓存内容也会受到配额和浏览器存储处理影响。可离线标签应依据本地可读取内容,不能承诺所有保存文件永久存在。用户清理浏览器数据后,应用要回到明确空状态,提供重新下载。

Service Worker 的生命周期

Service Worker 在独立 worker 环境运行,没有 DOM。部署通常需要 HTTPS,浏览器将 localhost 用作本地开发例外。注册路径与 scope 决定可控制的范围,放在深层目录的脚本不能随意接管整站。

首次注册经历安装和激活,安装时可以准备小规模必需资源。已有页面不一定立即受到新 worker 控制,需要检查 controller 或重新加载。worker 可能停止,内存全局变量不能作为长期下载记录。

更新时旧 worker 可以继续控制已打开页面,新 worker 安装后等待。若直接强制切换,旧 HTML 可能使用新脚本或已删除资源。更新策略应让页面和资源版本保持一致,并在用户安全保存输入后提示刷新。

在工程中加入离线层

先让普通学习站点在线可用,准备两篇公开正文和一张教学图片。通过 localhost 服务运行,建立 manifest 并在页面关联它;图标和安装体验按目标浏览器要求核对。安装与离线分别验收,不能只看到安装入口就宣布成功。

创建 worker 文件,从页面检查能力后注册。划定 scope,再实现安装处理,预缓存少量确定存在的资源。使用事件的完成管理机制让浏览器知道安装工作仍在进行;某个必要资源失败时不要把不完整缓存当作已准备好的版本。

随后增加 fetch 路由:仅处理约定方法、同源公开路径与可缓存响应,其他请求按原路网络发送。读取缓存时提供明确的离线缺失页面,避免返回一段看似成功的空正文。开发者工具中检查注册、控制页面和缓存内容,再做浏览器重启测试。

网络优先策略的离线模型

下面 Python 示例在有有效网络结果时更新缓存,网络不可用时读副本,没有副本时显示缺失。它仅是策略模型,没有 Service Worker、浏览器 Response、HTTP 状态和缓存生命周期。

cache = {"/learn/intro": "saved article"}
def read(path, online, response=None):
    if online and response is not None:
        cache[path] = response
        return response
    return cache.get(path, "offline unavailable")
assert read("/learn/intro", False) == "saved article"
assert read("/learn/new", False) == "offline unavailable"
assert read("/learn/intro", True, "updated") == "updated"
print(read("/learn/intro", False))

实际输出 updated。初次断网读到 saved article,未保存路径得到 offline unavailable,一次网络更新后断网读取新内容。真实 fetch 实现要判断响应是否成功、路径是否公开以及是否符合缓存条件,不能把任意失败响应存进去。

网络优先需要设定可接受的等待和失败处理。单纯依赖“在线”标志不可靠,设备连上网络仍可能无法访问服务。以实际请求结果决定回退,同时将是否显示旧版本反馈给页面。

版本更新与清理顺序

缓存名包含应用版本,安装新版本时写入新命名空间,激活时按明确规则清理不再需要的旧资源。清理只能覆盖应用拥有的缓存名称,不能遍历删除其他用途的数据,更不能把用户笔记当旧资源删掉。

对正文分别保存内容版本或更新时间来源,让“已保存版本”可以解释。更新失败时继续使用已经完整可读的版本,不提前删旧内容。页面显示更新提示,用户确认且输入保存后才进入刷新流程。

回退策略需要与资源保留规则配合。旧页面仍在运行时,删除它引用的版本文件可能造成加载失败。测试多标签页:一个保持旧页面,另一个获取新版本,观察 worker 等待与激活,确认两边都不陷入部分更新状态。

私人请求与错误边界

进度接口每次仍要验证身份,客户端缓存规则不构成授权。注销时清理与用户关联的本地状态,避免下一个使用者看到前一个人的数据。不要把令牌写进可分享 URL 或公共缓存键。

公共内容也要限制缓存规模。大型资料下载提供用户主动操作、进度和失败状态;安装预缓存不应塞入整个资料库。资源不存在、空间不足与网络失败分别提示恢复动作,防止无限重试占满容量。

开发时还需避免缓存隐藏修改。每轮测试记录当前worker版本和controller,必要时使用开发工具的更新或注销操作,但最终要重放正常用户更新路径。仅在开发工具强制刷新后成功,不能证明已打开页面自然切换也正确。

缓存路由要将响应与请求约定对应,例如只对GET正文读取复用副本。含查询参数的URL可能表示不同内容,规范化之前先说明参数语义,不能简单删除所有参数。响应的缓存与隐私限制也应遵守;遇到无法缓存的结果就按网络或缺失路径处理,而不是强行转换后放进公共缓存。

练习:完整验证四种访问

用浏览器开发工具测试首次访问就断网、在线访问后断网、正文更新后断网、旧标签页等待更新。首次无缓存时显示明确缺失,已保存版本可读,更新失败仍保留完整旧内容。

检查缓存中只出现允许的公共资源,私人进度响应不在其中。清理应用资源缓存后,用户结构化笔记不受影响。最后在目标浏览器与设备测试安装和重开,记录差异;Python 输出只能验证示例策略,不能证明安装性或实际 worker 控制行为。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。