一 前言
浏覽器一般有三類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)。
(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 Lifecyclehttps://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 workersmay 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) 強制更新
- 距離上一次更新檢查已超過24小時 , 會忽略浏覽器緩存, 強制到伺服器更新一次.
(2) 檢查更新(
Soft Update)
一般在下面情況會檢查更新,
- 第一次通路scope裡的頁面.
- .
- 有功能性事件發生, 比如push, sync.
- 在ServiceWorker URL發生變化時調用了.register()方法.
- ServiceWorker JS的緩存時間已超出其頭部的max-age設定的時間 (注: max-age大于24小時, 會使用24小時作為其值).
- ServiceWorker JS的代碼隻要有一個位元組發生了變化, 就會觸發更新, 包括其引入的腳本發生了變化.
我們看看浏覽器核心是怎樣實作周期性的檢查更新的.
ServiceWorker Schedule Update
|
從上述代碼流程可以看到, 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