缓存存储清理规则决定你的 PWA 是否能在存储压力下幸存
你的 PWA 的缓存内容可能被清理。以下是如何决定,以及如何让你的应用具有韧性。
为什么这很重要
浏览器维护各自的存储预算和清理策略。当存储压力来袭时——磁盘空间不足、用户操作、定期清理——你的 PWA 缓存的资产可能会在没有警告的情况下被清理。如果你的应用需要缓存的内容在离线时使用,而该内容被静默清理,你的离线功能就会中断。用户会责怪你的应用,而不会理解是浏览器存储策略执行的。
缓存存储层
Cache Storage 是你的 Service Worker 存储响应的地方:
const cache = await caches.open('v1');
await cache.addAll([
'/',
'/styles.css',
'/scripts.js',
'/images/logo.png'
]);它位于浏览器存储空间中,与 IndexedDB、Web Storage(localStorage/sessionStorage)和其他 API 一起。
浏览器清理策略
Chrome:
- 使用"清理压力"模型
- 在同源存储中使用最近最少使用(LRU)清理
- 当存储限制达到时清理整个源
- 清理阈值:约占所有浏览器源磁盘空间的 50-60%
Firefox:
- 使用 LRU 逻辑的每源清理
- 在磁盘空间较少时更积极
- 可以清理甚至活动 PWA 的缓存
- 用户可以从设置中手动清除站点数据
Safari:
- 以激进的缓存清理而闻名,特别是在 iOS 上
- 定期清理会自动删除旧缓存
- iStorage 比 Cache Storage 更容易被清理
- TWA 包(针对 Android)由于原生包装而具有更好的持久性
存储压力触发因素
浏览器因以下原因清理存储:
- 磁盘空间不足 - 当设备有不到约 100MB 的空间时
- 用户清除站点数据 - 浏览器设置中的手动操作
- 定期维护 - Safari 和 Firefox 清理旧缓存
- 超过存储配额 - 你的应用尝试存储超过允许的内容
你无法阻止触发因素 1、3 和 4。你只能设计韧性。
检测与估计
Storage Manager API:
const estimate = await navigator.storage.estimate();
console.log(`配额: ${estimate.quota} bytes`);
console.log(`使用量: ${estimate.usage} bytes`);
console.log(`使用百分比: ${Math.round(estimate.usage / estimate.quota * 100)}%`);持久化授予:
navigator.storage.persist().then((persistent) => {
if (persistent) {
console.log('存储持久化已授予');
} else {
console.log('持久化被拒绝 - 数据可能被清理');
}
});即使授予了持久化,清理仍然可能发生——只是可能性降低。
幸存策略
保持缓存精简:
- 只缓存离线功能必需的内容
- 对可以重新获取的内容使用临时缓存(不持久化)
- 实现缓存版本控制以删除旧版本
// 删除旧缓存版本
const cacheNames = await caches.keys();
for (const name of cacheNames) {
if (name !== currentCacheName) {
await caches.delete(name);
}
}检测并从清理中恢复:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(async (cacheNames) => {
// 检查我们的预期缓存是否存在
const hasCache = cacheNames.includes(currentCacheName);
if (!hasCache) {
// 缓存被清理,重新填充它
return caches.open(currentCacheName).then((cache) => {
return cache.addAll(essentialAssets);
});
}
})
);
});实现回退到网络:
当缓存内容缺失时,从网络获取并更新缓存:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request).then((response) => {
// 为下次缓存新响应
return caches.open(currentCacheName).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});清理评估标准
使用此检查清单评估风险:
| 因素 | 低风险 | 中风险 | 高风险 | |--------|---------|---------|---------| | 缓存大小 | < 10MB | 10-50MB | > 50MB | | 内容类型 | 仅静态资产 | 混合内容 | 用户生成的临时数据 | | 用户参与度 | 每日活跃 | 每周活跃 | 每月活跃 | | 平台 | Android(原生包) | 桌面 | iOS Safari |
高风险 + 不明确的回退 = 用户的离线体验中断。
监控缓存健康状况
在生产环境中追踪这些指标:
- 缓存命中率(有多少次获取从缓存提供)
- 关键资产的缓存未命中率
- 存储配额百分比随时间变化
- 清理检测(当预期缓存消失时)
- 重新缓存成功率
当以下情况时向开发人员发出告警:
- 缓存命中率降至 80% 以下
- 存储使用量持续超过配额的 70%
- 检测到关键缓存的清理
对开发者的建议
缓存清理是不可避免的。优雅地处理它,而不是试图阻止它。离线可靠性不是关于防止清理——而是关于检测它并透明地恢复。
将缓存存储视为易失性的。在 Service Worker 激活时实现重新缓存逻辑。设计页面使其在缓存内容缺失时也能工作(显示加载状态、按需获取、提供回退 UI)。
下一步
- 在你的安装/激活处理程序中实现缓存清理检测
- 将 Storage Manager API遥测添加到你的错误追踪
- 设计缓存必需资产与缓存可选资产
- 通过手动清除缓存并观察行为来测试清理场景
- 为值班工程师编写缓存恢复计划
你的 PWA 应该在缓存清理中幸存,而不是因为它而失败。