天天看點

PWA系列 - Service Workers 生命周期一 前言 二 生命周期 三 狀态管理 四 版本管理 五 腳本更新 六 線程退出 七 參考文檔

一 前言

浏覽器一般有三類Worker,

(1) Dedicated Worker, 專用worker, 隻能被建立它的JS通路. 建立它的頁面關閉, 它的生命周期就結束了.

(2) 

Shared Worker

, 共享worker, 可以被同一域名下的JS通路. 關聯的頁面都關閉時, 它的生命周期就結束了.

(3) 

ServiceWorker

, 是事件驅動的worker, 生命周期與頁面無關. 關聯頁面未關閉時, 它也可以退出, 沒有關聯頁面時, 它也可以啟動.

這三類Worker, 一個非常重要的差別在于不同的生命周期.  

與文檔無關的生命周期, 是它能提供可靠Web服務的一個重要基礎.

本文重點描述ServiceWorker如果管理它與文檔無關的生命周期, 以及如何管理各種版本狀态.

https://www.atatech.org/articles/78747#1 二 生命周期

官方文檔

提到, ServiceWorker生命周期的目的是,

  • 實作離線優先.
  • 在不打斷現有ServiceWorker的情況下,準備好一個新的ServiceWorker.
  • ServiceWorker注冊的scope下的頁面, 同一時間隻由一個ServiceWorker控制.
  • 確定你的網站隻有一個版本在運作.

Service Worker 可能有以下幾種狀态:解析成功(parsed),正在安裝(installing),安裝成功(installed),正在激活(activating),激活成功(activated),廢棄(redundant)。

PWA系列 - Service Workers 生命周期一 前言 二 生命周期 三 狀态管理 四 版本管理 五 腳本更新 六 線程退出 七 參考文檔

(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()
});           

(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

https://www.atatech.org/articles/78747#2 三 狀态管理

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狀态.

https://www.atatech.org/articles/78747#3 四 版本管理

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.

https://www.atatech.org/articles/78747#4 五 腳本更新

ServiceWorker注冊函數

中指定的scriptURL(比如, serviceworker.js), 會在什麼情況下請求更新呢? 一般有兩種更新方式.

(1) 強制更新

(2) 檢查更新(

Soft Update

)

一般在下面情況會檢查更新,

  • 第一次通路scope裡的頁面.
  • .
  • 有功能性事件發生, 比如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小時, 至少會更新一次.

https://www.atatech.org/articles/78747#5 六 線程退出

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

https://www.atatech.org/articles/78747#6 七 參考文檔

Stackoverflow - Service Worker vs Shared Worker The Service Worker Lifecycle - By Ire Aderinokun The Service Worker Lifecycle - By Jake Archibald