PWA系列 - Service Workers 生命周期

简介: ServiceWorker与文档无关的生命周期, 是它能提供可靠Web服务的一个重要基础。本文详细介绍ServiceWorker的生命周期和它在浏览器内核的一些实现细节。

一 前言

浏览器一般有三类Worker,

(1) Dedicated Worker, 专用worker, 只能被创建它的JS访问. 创建它的页面关闭, 它的生命周期就结束了.

(2) Shared Worker, 共享worker, 可以被同一域名下的JS访问. 关联的页面都关闭时, 它的生命周期就结束了.

(3) ServiceWorker, 是事件驱动的worker, 生命周期与页面无关. 关联页面未关闭时, 它也可以退出, 没有关联页面时, 它也可以启动.

这三类Worker, 一个非常重要的区别在于不同的生命周期.  ServiceWorker与文档无关的生命周期, 是它能提供可靠Web服务的一个重要基础.

本文重点描述ServiceWorker如果管理它与文档无关的生命周期, 以及如何管理各种版本状态.

二 生命周期

官方文档提到, ServiceWorker生命周期的目的是,

  • 实现离线优先.
  • 在不打断现有ServiceWorker的情况下,准备好一个新的ServiceWorker.
  • ServiceWorker注册的scope下的页面, 同一时间只由一个ServiceWorker控制.
  • 确保你的网站只有一个版本在运行.

Service Worker 可能有以下几种状态:解析成功(parsed),正在安装(installing),安装成功(installed),正在激活(activating),激活成功(activated),废弃(redundant)。

08f326c3cae549f0408f0bb25624c191668bd25a

(1) Service Worker 注册成功,navigator.serviceWorker.register返回成功, 并不意味着它已经完成安装或已经激活,仅仅是worker的脚本被成功解析,比如, 注册worker的URL与文档同源,协议是 HTTPS。

(2) Service Worker 注册成功后,会转入installing状态, 此时, install事件会被触发, 比较典型的做法是在install事件的处理函数中提前加载相关静态文件进缓存.

self.addEventListener('install', function(event) {  
  event.waitUntil(
    caches.open(currentCacheName).then(function(cache) {
      return cache.addAll(arrayOfFilesToCache);
    })
  );
});

如果有event.waitUntil方法, 必须等待它里面的操作成功完成, 否则会失败, 转入redundant状态.

(3) Service Worker 安装成功后, 会转入installed/waiting状态, 此时, ServiceWorker已准备好, 在等待接管页面已有的worker, 从而可以控制页面.

(4) Service Worker在满足下面条件之一时, 可以转入activating状态:

  • 没有active worker在运行.
  • JS调用self.skipWaiting()跳过waiting状态.
  • 用户关闭页面, 释放了当前处于active状态的worker.
  • 一定时间之后, 系统释放了当前处于active状态的worker.

在activating状态中, activate事件会触发, 比较典型的做法是在activate事件的处理函数中清理无用的缓存.

self.addEventListener('activate', function(event) {  
  event.waitUntil(
    // Get all the cache names
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        // Get all the items that are stored under a different cache name than the current one
        cacheNames.filter(function(cacheName) {
          return cacheName != currentCacheName;
        }).map(function(cacheName) {
          // Delete the items
          return caches.delete(cacheName);
        })
      ); // end Promise.all()
    }) // end caches.keys()
  ); // end event.waitUntil()
});

如果有event.waitUntil方法, 必须等待它里面的操作成功完成, 否则会失败, 转入redundant状态.

(5) Service Worker激活成功, 会转入active状态, 此时的worker已在控制页面的行为, 可以处理一些功能事件, 比如fetch, push, message.

self.addEventListener('fetch', function(event) {  
  // Do stuff with fetch events
});

self.addEventListener('message', function(event) {  
  // Do stuff with postMessages received from document
});

(6) Service Worker在满足下面条件之一时, 会转入redundant状态:

  • 安装失败
  • 激活失败
  • 被新的Service Worker取代

本节内容参考 The Service Worker Lifecycle

三 状态管理

ServiceWorker在浏览器内核有两类状态, 一类是ServiceWorker线程的运行状态, 另一类是ServiceWorker脚本版本的状态.

(1) ServiceWorker线程的运行状态, 一般对应ServiceWorker线程的状态. 这类状态只保存在内存中.

  • STOPPED: 已停止, EmbeddedWorkerInstance::OnStopped时设置.
  • STARTING: 正在启动, EmbeddedWorkerInstance::Start时设置.
  • RUNNING: 正在运行, EmbeddedWorkerInstance::OnStarted时设置.
  • STOPPING: 正在停止, EmbeddedWorkerInstance::Stop --> EmbeddedWorkerRegistry::StopWorker返回status为SERVICE_WORKER_OK时设置.

(2) ServiceWorker脚本版本(即注册函数中指定的serviceworker.js)的状态, 这类状态中的INSTALLED和ACTIVATED可以被持久化存储.

  • NEW:  浏览器内核的ServiceWorkerVersion已创建, 属于一个初始值.
  • INSTALLING:  Install事件被派发和处理. 一般在ServiceWorker线程启动后, 即ServiceWorkerVersion::StartWorker返回status为SERVICE_WORKER_OK时设置.
  • INSTALLED:  Install事件已处理完成, 准备进入ACTIVATING状态. 一般在注册信息已存储到数据库, 即ServiceWorkerStorage::StoreRegistration返回status为SERVICE_WORKER_OK时设置.
  • ACTIVATING:  Activate事件被派发和处理. 一般在当前scope下没有active ServiceWorker或INSTALLED状态的ServiceWorker调用了skipWaiting, ServiceWorker就会从INSTALLED状态转为ACTIVATING状态.
  • ACTIVATED:  Activate事件已处理完成, 已正式开始控制页面, 可处理各类功能事件. 一般在activate事件处理完成后就会转为ACTIVATED状态, 此时ServiceWorker就可以控制页面行为, 可以处理功能事件, 比如, fetch, push.
  • REDUNDANT: ServiceWorkerVersion已失效, 一般是因为执行了unregister操作或已被新ServiceWorker更新替换. 

注1: ServiceWorker规范 中提到的 "service workers may be started and killed many times a second", 指的是ServiceWorker线程随时可以Started和Killed. 在关联文档未关闭时, ServiceWorker线程可以处于Stopped状态; 在全部关联文档都已关闭时, ServiceWorker线程也可以处于Running状态.

注2: ServiceWorker脚本版本的状态, 也是独立于文档生命周期的, 与ServiceWorker线程的运行状态无关, ServiceWorker线程关闭时, ServiceWorker脚本版本也可处于ACTIVATED状态.

注3: ServiceWorker脚本版本的状态, INSTALLED和ACTIVATED是稳定的状态, ServiceWorker线程启动之后一般是进入这两种状态之一. INSTALLING和ACTIVATING是中间状态, 一般只会在ServiceWorker新注册或更新时触发一次, 刷新页面一般不会触发. INSTALLING成功就转入INSTALLED, 失败就转入REDUNDANT. ACTIVATING成功就转入ACTIVATED, 失败就转入REDUNDANT. 

注4: 如果ServiceWorker脚本版本处于ACTIVATED状态, 功能事件处理完之后, ServiceWorker线程会被Stop, 当再次有功能事件时, ServiceWorker线程又会被Start, Start完成后ServiceWorker就可以立即进入ACTIVATED状态.

四 版本管理

ServiceWorker脚本版本, 浏览器内核会管理三种版本.

(1) installing_version: 处于INSTALLING状态的版本

(2) waiting_version: 处于INSTALLED状态的版本

(3) active_version: 处于ACTIVATED状态的版本

installing_version 一般是在ServiceWorker线程启动后, 即ServiceWorkerVersion::StartWorker返回status为SERVICE_WORKER_OK时, 处于此版本状态, 这是一个中间版本, 在正确安装完成后会转入waiting_version.

waiting_version 一般在注册信息已存储到数据库, 即ServiceWorkerStorage::StoreRegistration返回status为SERVICE_WORKER_OK时, 处于此版本状态. 或者在再次打开ServiceWorker页面时, 检查到ServiceWorker脚本版本的状态为INSTALLED, 也会进入此版本状态. waiting_version 的存在确保了当前scope下只有一个active ServiceWorker.

active_version 一般在activate事件处理完成后, 就会处于此版本状态, 同一scope下只有一个active ServiceWorker. 需要特别注意的是, 当前页面已有active worker控制, 刷新页面时, 新版本Waiting(Installed)状态的ServiceWorker并不能转入active状态. 

ServiceWorker可以从waiting_version转入active_version的条件:

  • 当前scope下没有active ServiceWorker在运行.
  • 页面JS调用self.skipWaiting跳过waiting状态.
  • 用户关闭页面, 释放了当前处于active状态的ServiceWorker.
  • 浏览器周期性检测, 发现active ServiceWorker处于idle状态, 就会释放当前处于active状态的ServiceWorker.

五 脚本更新

ServiceWorker注册函数中指定的scriptURL(比如, serviceworker.js), 会在什么情况下请求更新呢? 一般有两种更新方式.

(1) 强制更新

(2) 检查更新(Soft Update)

一般在下面情况会检查更新,

  • 第一次访问scope里的页面.
  • 距离上一次更新检查已超过24小时.
  • 有功能性事件发生, 比如push, sync.
  • 在ServiceWorker URL发生变化时调用了.register()方法.
  • ServiceWorker JS的缓存时间已超出其头部的max-age设置的时间 (注: max-age大于24小时, 会使用24小时作为其值).
  • ServiceWorker JS的代码只要有一个字节发生了变化, 就会触发更新, 包括其引入的脚本发生了变化.

我们看看浏览器内核是怎样实现周期性的检查更新的.

ServiceWorker Schedule Update
ServiceWorkerControlleeRequestHandler::~ServiceWorkerControlleeRequestHandler 
// Navigation triggers an update to occur shortly after the page and its initial subresources load.
--> ServiceWorkerVersion::ScheduleUpdate   // if (is_main_resource_load_)
--> ServiceWorkerVersion::StartUpdate

从上述代码流程可以看到, ServiceWorker页面主文档加载完成时, 就会触发active_version的一次检查更新, 如果距离上一次脚本更新的时间超过了24小时, 就会设置LOAD_BYPASS_CACHE的标记, 忽略浏览器缓存, 直接从网络加载.

上一次脚本更新的时间, 一般在ServiceWorker安装完成时会更新为当前时间, 或者检查到脚本超过24小时都没有发生变化也会更新为当前时间, 这样就能保证ServiceWorker在安装完成之后, 每隔24小时, 至少会更新一次.

六 线程退出

ServiceWorker线程一般在什么情况下会被停止呢?

(1)ServiceWorker JS有任何异常,都会导致ServiceWorker线程退出。包括但不限于, JS文件存在语法错误, ServiceWorker安装失败/ 激活失败,ServiceWorker JS执行时出现未捕获的异常。

(2)ServiceWorker 功能事件处理完成,处于空闲状态,ServiceWorker线程会自动退出。

(3)ServiceWorker JS执行时间过长,ServiceWorker线程会自动退出。比如, ServiceWorker JS执行时间超过30秒,或Fetch请求在5分钟内还未完成。

(4)浏览器会周期性检查各个ServiceWorker线程是否可以退出, 一般在启动ServiceWorker线程30秒会检查一次,杀掉空闲超过30秒的ServiceWorker线程。

(5)为了方便开发者调试, Chromium进行了特殊处理, 在连上devtools之后,ServiceWorker线程不会退出。
参考:Keep a serviceworker alive when devtools is attached

七 参考文档

Stackoverflow - Service Worker vs Shared Worker

The Service Worker Lifecycle - By Ire Aderinokun

The Service Worker Lifecycle - By Jake Archibald

目录
相关文章
|
3月前
|
安全 Android开发 开发者
Service与Activity如何实现通信
Android为Service与Activity之间的通信提供了多种灵活的方式,开发者可以根据应用程序的需求选择合适的通信机制。对于多数简单通信需求,Intent和Binder通常就足够使用。另外,要注意线程安全和数据同步的问题,尤其是在多线程环境下操作UI或Service中的共享资源时。
162 0
|
4月前
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
61 1
|
4月前
|
缓存 前端开发 JavaScript
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第10天】在Web开发中,Web Workers和Service Worker提升性能。Workers运行后台任务,防止界面冻结。Web Workers处理计算密集型任务,Service Worker则缓存资源实现离线支持。常见问题包括通信故障、资源限制、注册错误及缓存更新。通过示例代码展示了两者用法,并强调生命周期管理和错误处理的重要性。善用这些技术,可构建高性能的Web应用。
82 0
|
11月前
|
存储 Web App开发 缓存
WorkBox 之底层逻辑Service Worker(二)
WorkBox 之底层逻辑Service Worker(二)
167 0
|
11月前
|
存储 缓存 前端开发
WorkBox 之底层逻辑Service Worker(一)
WorkBox 之底层逻辑Service Worker(一)
|
缓存 JavaScript 前端开发
在项目中使用Service Worker 与 PWA
在项目中使用Service Worker 与 PWA
86 1
|
存储 缓存 前端开发
Service Worker实现离线缓存和推送通知
离线缓存和推送通知在提升网页的离线访问体验方面起着重要的作用。 离线缓存允许网页将所需的资源(如 HTML、CSS、JavaScript 文件、图像等)保存在用户设备的本地存储中。这意味着即使在没有网络连接的情况下,用户仍然可以访问网页的内容和功能。离线缓存不仅提供了更好的用户体验,而且还可以减轻服务器的负担,因为客户端可以直接通过本地缓存的资源进行加载,而无需每次都向服务器发出请求。
650 0
|
缓存 Kubernetes 数据库
【kubernetes】Service: 将外部服务定位为 Service
【kubernetes】Service: 将外部服务定位为 Service
121 0
|
Kubernetes 负载均衡 网络协议
k8s 【网络组件】Service使用详解(2)
k8s 【网络组件】Service使用详解(2)
k8s 【网络组件】Service使用详解(2)
|
Kubernetes 负载均衡 网络协议
K8S 集群核心概念 Service_通过资源清单文件创建 Service_ClusterIP | 学习笔记
快速学习 K8S 集群核心概念 Service_通过资源清单文件创建 Service_ClusterIP
169 0
K8S 集群核心概念 Service_通过资源清单文件创建 Service_ClusterIP | 学习笔记