天天看點

Service Worker最佳實踐

Service Worker是一項比較新的Web技術,是Chromium團隊在吸收了ChromePackaged App的Event Page機制,同時吸取了HTML5 AppCache标準失敗的教訓之後,提出一套新的W3C規範,旨在提高WebApp的離線緩存能力,縮小WebApp與NativeApp之間差距。

Service Worker從英文翻譯過來就是一個服務勞工,服務于前端頁面的背景線程,基于Web Worker實作。有着獨立的Javascript運作環境,分擔、協助前端頁面完成前端開發者配置設定的需要在背景悄悄執行的任務。基于它可以實作攔截和處理網絡請求、消息推送、靜默更新、事件同步等服務。

X5核心作為WebView提供給不同app使用,具備Service Worker網絡攔截和處理網絡請求,配合CacheStorage可以實作web頁面的緩存管理以及與前端通過PostMessage通信。

Service Worker技術核心是Service Worker腳本,它 是一種由Javascript編寫的浏覽器端代理腳本。

前端頁面向核心發起注冊時會将腳本位址通知核心,核心會啟動獨立進/線程加載Service Worker腳本并執行Service Worker安裝及激活動作。成功激活後便進入空閑等待狀态,若目前的Service Worker進/線程一直沒有管轄的頁面或者事件消息時會自動終止(具體的終止政策視不同浏覽器及版本而定,不會影響前端編寫邏輯,但前端勿在Service Worker腳本中儲存需要持久化的資訊,可以借助localstorage),當打開新的可管轄頁面或者已管轄頁面發起message等消息時,Service Worker進/線程會被重新喚起。

每當已安裝的Service Worker有管轄頁面被打開時,便會觸發Service Worker腳本更新,當上次腳本更新寫入Service Worker資料庫的時間戳與本次更新超過24小時,便會忽略本地網絡cache的Service Worker腳本直接從網絡拉取。若網絡拉取的與本地有一個位元組的差異都會觸發Service Worker腳本的更新,更新流程與安裝類似,隻是在更新安裝成功後不會立即進入active狀态,需要等待舊版本的Service Worker進/線程終止。

Service Worker最佳實踐

有過使用chrome inspect前端頁面調試經驗對于Service Worker開發調試就很容易上手了,以 offline-page 為例:

使用pc chrome浏覽器(最好是M53以後版本)打開上述頁面後,按F12鍵進入inspect調試模式後

Service Worker最佳實踐

圖2

單擊圖2 inspect調試界面中的1及2後會出現目前頁面域下的所有Service Worker,在單擊6就會進入圖3界面,這個時候調試Service Worker腳本就如調試前端頁面js一樣,可以随意在Service Worker腳本中下斷點。如果在fetch監聽事件中打上斷點,當頁面重新整理或者頁面中有其它請求時便會到達Service Worker線程,使得Service Worker腳本中的fetch事件執行被中斷,這時可以将滑鼠移動到fetch事件中的event上便可以看到是什麼樣的請求、請求的url等 。

Service Worker最佳實踐

圖3

對于一些較為複雜的頁面,往往會有一部分資源使用本地cache,還有一部分仍然需要是時拉取,在調試過程中勾選圖2中的3、4來快速達到目前tab頁離線和跳過Service Worker攔截。

在之前的原理中說過,Service Worker會在每次打開對應的頁面後去檢查更新Service Worker腳本,但如果Service Worker腳本有緩存期限的話,那麼在開發調試的時候修改了測試頁面的Service Worker腳本并push到測試頁面伺服器上之後,重新整理頁面并不能立即去網絡更新腳本,給開發調試帶來麻煩,但圖2中的5可以幫助開發強行忽略本地Service Worker腳本cache,實時的去網絡更新。

Service Worker最佳實踐

圖4

Service Worker可以緩存資源,點選圖2中的7便可以看到圖4展示,表明了目前浏覽器對目前頁面的資源緩存情況,可以通過滑鼠右鍵特定資源對資源進行删除操作。

Service Worker最佳實踐

圖5

最後我們可以通過在浏覽器中輸入chrome://serviceworker-internals來了解目前浏覽器中所有已安裝Service Worker的詳細情況,Installation Status及Running Status展示目前Service Worker安裝狀态及運作狀态,Version ID為其在目前資料庫中的一個配置設定到的版本号,後續Service Worker腳本更新,版本号也會一起提升。

還是以 offline-page 為例,前端在原來的web應用中使用Service Worker隻需要三大步

1、切入到https;由于Service Worker可以劫持連接配接,僞造和過濾響應,是以保證其在傳輸過程中不被篡改非常重要。

2、在頁面加載的恰當時機注冊Service Worker;示例中在index頁面的body onload事件中注冊了同path下的service-worker.js作為index頁面的服務線程腳本。

Service Worker最佳實踐

圖6

3、編寫serviceworker腳本邏輯;serviceworker是事件驅動型服務線程,是以serviceworker腳本邏輯中基本都是以事件監聽作為邏輯入口,示例中在serviceworker腳本被安裝的install事件中緩存index頁面主資源及子資源,

Service Worker最佳實踐

圖7

在激活事件activate監聽事件中清除曆史緩存,在這裡需要注意的是caches.keys周遊的是目前域下所有的cache,可能同域下的其它path也使用了Service Worker進行資源緩存,直接如圖所為,可能會誤删除。

Service Worker最佳實踐

圖8

在fetch事件中,攔截前端頁面發起的資源請求并到之前緩存的cache中比對。其中加上容錯處理,當發現緩存中無目前所要請求的資源時,折回網絡請求。

Service Worker最佳實踐

圖9

了解Service Worker資源的幾種緩存政策是使用好Service Worker進行資源緩存的基礎,實際應用場景會是幾種緩存政策的集合。

4.2.1 不影響安裝的資源預緩存

對于某些固定不變的靜态資源,我們習慣在Service Worker初次安裝的install事件中将其緩存,但資源過大或者網絡不佳都會造成資源并未全部下載下傳成功而導緻Service Worker安裝被中斷,隻有等下次使用者在打開相應頁面。這裡可以将靜态資源按優先級分為兩類,一類是重要資源,一類是非重要資源,将重要資源放到安裝等待隊列中,非重要資源放到獨立的隊列中,這樣隻需要重要資源全部都加載成功就可以成功安裝Service Worker了,可以提高Service Worker安裝成功率。

offline-page-not-dependent-on-install

Service Worker最佳實踐

圖10

4.2.2 漸進式緩存

對于在install中發現沒有緩存,頁面又依賴但又不經常變化的資源,可以在頁面打開或發生使用者互動時觸發fetch然後使用fetch api再去網絡拉取,将傳回正常的response緩存起來以便下次使用。

progressive-cache

Service Worker最佳實踐

圖11

4.2.3 僅使用緩存

在fetch事件中,僅去比對資源,若比對失敗,表現出來的就是前端頁面對于該 資源加載失敗。這裡容錯性比較差,适合頁面資源都是靜态資源的,且不能使用不影響安裝的資源預緩存。

cache-only

Service Worker最佳實踐

圖12

4.2.4 僅使用網絡

在fetch事件中,僅将request重新抽出用fetch去網絡加載并傳回給前端頁面。适用于資源大多是動态資源、實時性要求高的場景。

network-only

Service Worker最佳實踐

圖13

4.2.5 緩存優先

簡單的資源緩存中使用的就是緩存優先政策,先去緩存比對,比對失敗折回網絡,這算是最常用、容錯性能好的一種政策。

offline-page

4.2.6 網絡優先

在fetch事件中先去網絡fetch,當出現伺服器故障或者網絡不良時,折回本地緩存,目的是為了展示最新的資料,對實時性要求比較高但又能夠帶來良好體驗的應用,比如天氣類型應用。

network-first

Service Worker最佳實踐

圖14

4.2.7 速度優先

在fetch事件中同時發起本地緩存比對及網絡請求,誰先傳回使用誰的,該方案适用于對性能要求比較高的站點,縮短了緩存優先政策中有可能緩存中沒有資源再折回網絡的時間消耗。

speed-first

Service Worker最佳實踐

圖15

Service Worker可以攔截它管轄範圍内的基本所有請求,跨域資源也不例外。在普通的頁面中,包含幾個跨域腳本、圖檔等資源也太正常了。那麼如何對這些跨域資源進行緩存呢?

首先,浏覽器預設對跨域資源發起的是ncors請求,也就是得到的response是opaque的,Service Worker是無法獲得該response的status及url資訊,以至于該response是否成功不得而知。如果對跨域資源能夠發起cors請求,在跨域伺服器允許的情況下,得到部分屬性status及url可見的response,就可以判斷出跨域請求是否成功,是否可以進行緩存以備下次使用了。朝着這個思想:

Service Worker最佳實踐

圖16

1. 首先保證跨域的資源來自安全的https位址;

2. 保證跨域資源伺服器的response中Access-Control-Allow-Origin中包含目前的頁面所在域或者為*;

3. 對于前端頁面中的跨域資源的url可以附帶“cors=1”參數,以便Service Worker在攔截之後可以判斷出是跨域請求進而重新進行組裝cors請求,如圖17。

Service Worker最佳實踐

圖17

cross-resources

首次通路解決方案旨在使用者通路業務前實作業務的資源緩存,讓使用者在第一次真正通路業務時能夠讓業務頁面以最快的速度展示出來。針對該主旨,X5核心實作了三套具體實作方案:

Service Worker最佳實踐

圖18

5.1.1 離線包方式

離線包原理就是先在X5核心上模拟打開一次業務網址,然後将Service Worker中的cachestorage緩存、注冊資訊及腳本資訊資料庫進行打包内置到宿主中,當宿主首次安裝時将離線包路徑告知核心,核心會自動将離線包拷貝解壓到核心目錄。相當于使用者跳過了首次通路安裝Service Worker的過程。以QQ浏覽器為例:

業務側:

1、前端業務需要建立基于Service Worker業務,并且業務可以通過SW實作離線通路,在SW腳本的install方法中需要做資源的緩存。

2、可以通過QB6.8及以上版本通路swtool.qq.com,點選“離線包生成環境初始化”後重新開機浏覽器,輸入業務網址直到提示離線包已生成後退出浏覽器,這時離線包已生成在/sdcard/tbs/com.tencent.mtt/out/Service Worker.zip。

3、關閉網絡進入QQ浏覽器,通過設定清除緩存檔案後再将生成的離線包拷貝至/sdcard/tbs/com.tencent.mtt/Service Worker.zip位置并重新啟動浏覽器通路業務網址,如果業務可以正常打開,說明離線包OK。

4、若業務需要将離線包打進宿主apk,隻要将離線包丢給宿主即可。

X5核心宿主側:

1、若TBS宿主隻有單個業務離線包打進apk,那麼宿主需要在宿主apk首次安裝并且在TBS核心初始化之前将業務離線包拷貝至/sdcard/tbs/{宿主包名}/Service Worker.zip即可,TBS啟動時會自動搜尋該路徑。

2、若TBS宿主有多個單業務的離線包需要打進apk,可以由X5核心側協助完成合并(後續會有自動化合并站點工具),将多個單業務離線包合并成一個後再按照單業務的方式處理。

3、TBS宿主在釋出前需要參考業務使用QB自檢的方式自檢業務是否可以離線打開。

5.1.2 X5核心背景雲下發指令

1、前端業務需要建立基于Service Worker業務,并且業務可以通過SW實作離線通路,在SW腳本的install方法中需要做資源的緩存

2、需要将提前預置的業務網址及Service Worker腳本url及宿主包名同步給X5核心團隊(目前接口負責人zhengyuli、yongling),稽核通過後随時可以上線。

5.1.3 X5核心擴充接口

使用TBS的宿主可以調用X5核心擴充接口webview.getX5WebViewExtension().registerServiceWorkerBackground(String url, String scriptUrl)來提前向核心注冊某一特定頁面的Service Worker。

對于一些更新比較頻繁,實時性要求較高的業務,對更新能力需求也是相當亟需。一方面能讓使用者能夠及時看到最新消息(Service Worker目前自帶的更新能力隻會發生在目前通路後,隻有下次才能看到新更新的頁面),另一方面能夠緩解對業務伺服器的并發通路,還能夠緩解使用者的網絡慢導緻進行業務更新時長時間等待。具體實作架構見圖17。

Service Worker最佳實踐

圖19

5.2.1 TBS背景雲下發指令

1、前端業務需要驗證業務在更新Service Worker腳本後是否可以正常通路

2、需要将提前預置的業務網址及Service Worker腳本url及宿主包名和更新時間間隔同步給X5核心團隊(目前接口負責人zhengyuli、yongling),稽核通過後随時可以上線。

5.2.2 TBS webview擴充接口

使用TBS的宿主可以調用X5核心擴充接口webview.getX5WebViewExtension().updateServiceWorkerBackground(String url)來提前向核心申請更新特定業務的Service Worker(注意這裡需要業務已經在核心中安裝過Service Worker,切此接口調用一次隻會強制更新一次)。

通常app在使用webview時,為了提升展示頁面的速度,一般都會使用webview的shouldInterceptRequest來攔截webview的資源請求,然後将本地的資源校驗後丢給webview處理。因為shouldInterceptRequest從核心通知到app,需要統一在File線程中排隊并經曆好幾個線程的中轉到達app,對于資源并發請求較少的頁面來說,這種瓶頸可能并不明顯,對于頁面較複雜并發請求較多的頁面來說,首屏加載完成的時間并不能像打開本地頁面那樣“秒開”。

X5核心借鑒Service Worker原理,允許app提前将業務的資源放入到核心緩存,當業務被通路時,會先通路本地cache,有就直接傳回給核心,沒有就跳過shouldInterceptRequest直接去網絡加載,來屏蔽shouldInterceptRequest性能瓶頸。在手機QQ上的個别業務上有首屏性能有25%左右的提升。

使用TBS的宿主在空閑時間(不要在打開業務的時候,轉入核心緩存需要消耗資源)可以調用X5核心擴充接口

webview.getX5WebViewExtension().registerServiceWorkerOffline(String url, List paths, boolean deleteAllCacheBefore)

其中url為業務網址,paths為絕對緩存路徑,如https://host.com/path1/path2/img.png 需要建立/***/cache/host.com/path1/path2/img.png 這樣的路徑,并将/***/cache傳入核心,因為是list,是以支援多個目錄,比如/***/cache1、/***/cache2等。deleteAllCacheBefore 删除該業務之前也有緩存,因為在更新資源時也是調用同樣的接口,是以可能會出現核心緩存的備援。

另外可以調用webview.getX5WebViewExtension().clearServiceWorkerCache()[将會在TBS3.3上提供]來清除轉入核心的緩存,包括注冊資訊,以便能夠動态的恢複到之前的狀态。