文章

理解 Service Workers 生命周期
2018/08/27

约定

对于 Service Workers 以下简称为 SW

理解 SW 生命周期的目的

  • 可以实现优先加载离线缓存(以实现更快的加载速度和提高可访问性)
  • 可以实现新版本在后台默默准备就绪,同时不干扰当前版本的可访问性
  • 可以保证 SW 自始至终生效或不生效
  • 可以保证用户在进入网站那一刻开始终获得同一个版本(不会因为在某个时间点加载下一个 section 突然获取了开发者刚刚发布的新版本。这样可能会造成数据丢失等问题,总之非常影响用户体验。)

register

  • 你准备好的 SW.js(可以是任何你自定义的名字)脚本会被在程序调用 register('URL') 函数时被下载、解析、运行
  • 这个过程可以在 Developer Tools - Application 里被看到

install

  • install 是 SW.js 脚本被下载、解析(统称为 register)后第一个被触发的事件
  • 一个 SW 只能同时运行一个 install 事件
  • 后台下载并缓存所有作用域内的静态文件就发生在 install 事件的这个过程中
  • 如果 install 失败了那 SW 就不会生效,处于 install 之后的 fetchpush 事件也不可能发生,你的网站仍然从远程服务器加载资源

activate

  • install 事件完成紧接着就会发生 activate 事件(非更新情况下)
  • fetchpush 事件只有在 installactivate 事件成功之后才能发挥作用
  • 当你的网站第一次 register => install => activate 完成后,fetchpush 事件已经准备就绪了,但接下来的资源加载仍然不会走 SW 缓存。因为「统一性」是 SW 的原则之一,你必须刷新网站重新访问,SW 才能掌控你的客户端,发挥真正的作用。( clients.claim() 函数可以针对这个特点做出调整,具体查看官方文档

update

  • update(检测更新)事件有三个触发场景
    • 浏览器加载 SW 作用域内的页面时会触发
    • 24小时内没有检测过更新,则调用 pushsync 函数时会触发
    • SW.js 脚本的 URL 和之前加载的不同,当调用 register() 函数时会触发
  • 如果检测到 SW.js 脚本的数据内容和之前加载的相比有变动,则会被认为这是一个新版本的 SW
  • 新版本的 SW 在被成功注册后会默默的独立开始 install,运行自己的生命周期
  • 如果新版本的 SW 由于各种原因无法成功 install,那它将会被废弃,但并不耽误旧版本的 SW 继续工作
  • 手动更新:reg.update()

waiting

  • 新版本的 SW install 成功后并不会立即 activate,会进入 waiting 状态,他会在当前机器所有有关该网站的客户端全部关闭后在下一次加载时 activate 生效
  • 正是 waiting 状态可以保证客户端版本实现「统一性原则」
  • self.skipWaiting() 函数会使 install 完成的新版本 SW 跳过 waiting 立即 activate

ENDING TIPS

  • 避免在 <script> 里更改 register('URL') 作为参数的 SW.js 脚本的 URL,因为 SW 是基于缓存加载的,所以你更改之后的 URL 可能永远无法被用户的浏览器加载,除非你又更改了之前的 SW.js 脚本让其更新。
  • 一些很有用的监视 API
navigator.serviceWorker.register('/sw.js').then(reg => {
  reg.installing; // 表示安装状态的 SW ,或者为 undefined
  reg.waiting; // 等待状态的 SW ,或者为 undefined
  reg.active; // 激活状态的 SW ,或者为 undefined

  reg.addEventListener('updatefound', () => {
    // 用 reg.installing 可以捕捉到新的 SW
    const newWorker = reg.installing;

    newWorker.state;
    // "installing" - install 正在进行但还没完成
    // "installed"  - install 已经完成
    // "activating" - activate 正在进行但还没完成
    // "activated"  - activate 已经完成
    // "redundant"  - 工作不正常或已经被废弃

    newWorker.addEventListener('statechange', () => {
      // 当 newWorker.state 发生变化时
      // do something...
    });
  });
});

navigator.serviceWorker.addEventListener('controllerchange', () => {
  // 当生效的 SW 发生变化时
  // do something...
});

参考文章