Service worker 生命周期:install、activate 与更新
一句话: service worker 在控制页面之前会经历 注册 → 安装 →(等待)→ 激活。新版本
会在后台安装,但会停在 waiting 状态,直到使用旧版本的所有标签页关闭——因此更新是
原子的、绝不会半途生效,除非你主动调用 skipWaiting() 与 clients.claim()。
为什么需要生命周期
Section titled “为什么需要生命周期”- 原子更新。 页面的 HTML、JS 与缓存它们的 service worker 是一起版本化的。通过让 新 worker 停在 waiting 直到旧客户端全部消失,浏览器避免了标签页用新代码搭配旧 缓存(或反之)。
- 离线安全。 worker 只有在
activate成功后才接管,因此失败的安装永远不会替换掉 可用的离线副本。在新 worker 完全就绪前,旧 worker 持续提供服务。 - 不会突然接管。 刚安装的 worker 默认不会控制已打开的标签页。现有页面保留当前 worker,使整个会话期间行为保持稳定。
- 注册(Register)。
navigator.serviceWorker.register(url, { scope })让页面指向 一个 worker 脚本。注册是幂等的,并绑定到一个 scope。 - 安装(Install)。
install事件每个 worker 版本只触发一次——是用event.waitUntil(...)预缓存应用外壳的时机。安装失败会丢弃该 worker。 - 等待(Waiting)。 若已有处于活动状态的 worker 正在控制客户端,新(已安装的) worker 会进入 waiting。在被旧 worker 控制的所有标签页关闭前,它不会激活。这就是 经典的“关掉每个标签页之前更新一直卡住”的现象。
- 激活(Activate)。 worker 接管时触发
activate事件——是清理旧缓存的时机。在 激活前,worker 不处理fetch。 - 空闲 / 终止。 事件之间,浏览器可能停止 worker 以节省内存,并在下次事件时重启。 绝不要依赖跨事件的内存状态。
浏览器与生态支持
Section titled “浏览器与生态支持”| Browser / Platform | Support | Since | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | ✅ yes | 40 | high | ref | — |
| Chrome (Desktop) | ✅ yes | 40 | high | ref | — |
| Edge (Desktop) | ✅ yes | 17 | high | ref | — |
| Safari (iOS) | ✅ yes | 11.3 | high | ref | Storage may be evicted after prolonged non-use. |
| Safari (macOS) | ✅ yes | 11.1 | high | ref | — |
| Firefox (Desktop) | ✅ yes | 44 | high | ref | — |
| Samsung Internet | ✅ yes | 4.0 | high | ref | — |
控制接管时机
Section titled “控制接管时机”| 目标 | 机制 | 含义 |
|---|---|---|
| 仅在所有标签页关闭后才应用更新(最安全) | 什么都不做——默认等待行为。 | 用户在下次完全重启应用时拿到新版本;不会发生会话中途的切换。 |
| 立即激活新 worker | 在 install 中调用 self.skipWaiting()。 |
跳过 waiting;需谨慎搭配,确保新 worker 的缓存与已加载页面匹配。 |
| 接管已存在但未受控的标签页 | 在 activate 中调用 self.clients.claim()。 |
注册后的首次加载无需刷新即可受控。 |
| 强制检查更新 | registration.update(),或普通刷新。 |
浏览器重新拉取 worker 脚本;字节不同的脚本会安装为新版本。 |
| 新 worker 上线时刷新所有标签页 | 监听 controllerchange,然后 location.reload()。 |
避免 skipWaiting() + claim() 后出现新 worker 配旧页面的错配。 |
- 在
install中用event.waitUntil(...)预缓存应用外壳。 - 在
activate中用event.waitUntil(...)删除过期缓存。 - 决定更新策略:默认等待(安全)还是
skipWaiting()(立即)。 - 若使用
skipWaiting(),同时处理controllerchange以避免版本混用。 - 仅在需要首次加载即受控且不刷新时,才使用
clients.claim()。 - 对不想发布的部署,保持 worker 脚本字节稳定——任何字节变更都会触发新安装。
- 将 worker 视为无状态;任何需持久化的数据存入 Cache Storage 或 IndexedDB。
这对信任意味着什么
Section titled “这对信任意味着什么”生命周期正是让 PWA 像原生应用一样可靠的原因:更新要么干净落地、要么完全不生效,离线 体验也绝不会在更新途中损坏。理解 waiting 能解释最常见的困惑——“我已经部署了,但用户 仍看到旧版本”——并让你在“安全的渐进上线”与“立即上线”之间做出有意识的选择。