(資料圖)
導讀|H5開屏龜速常是令開發者頭疼的問題。騰訊企業微信團隊對該現象進行分析優化,最終H5開屏耗時130ms,達到秒開效果!企微前端開發工程師陳智仁將分享可用可擴展的Hybird H5秒開方案。該團隊使用離線包解決了資源請求耗時的問題,在這個基礎上通過耗時分析找到瓶頸環節,進一步采用“預熱”進行優化提速以解決了WebView初始化、數據預拉取、js執行(app初始化)耗時的問題。希望這些通用方法對你有幫助。
背景
服務端渲染(SSR)是Web主流的性能優化手段。SSR直出相比傳統的SPA應用加載渲染規避了首屏拉取數據和資源請求的網絡往返耗時。團隊針對Web開發也已經支持了SSR能力。近期出于動態化運營的考慮,我們選擇了Web開發,同時我們也接到了提升體驗的訴求。以企業微信要開發的頁面為例:采用SSR方案,從用戶點擊到首屏渲染的耗時均值約600ms,白屏時間的存在是可以感知到的。為了盡可能消除白屏達到秒開效果,我們嘗試做更多探索。方案思路1) 方案選型如何實現頁面秒開呢?從最直觀的渲染鏈路來入手分析。下圖列出了從用戶點擊到看到首屏渲染可交互,一個SPA應用主要環節的加載流程。我們調研了業內相關方案,從渲染鏈路的視角來看下常見方案的優化思路。傳統離線包在加載渲染過程中,網絡IO是很明顯的一個耗時瓶頸。傳統的離線包方案思路很直接,如果網絡耗時那就將資源離線,很好地解決了資源請求的耗時。用Service Worker也能達到離線包的效果,同時也是Web標準。首次渲染優化一般需要結合客戶端配置預啟動腳本來達到緩存資源的效果。SSRSSR則從另外的角度出發,在請求頁面的時候就進行服務端數據拉取和頁面直出,首屏得以在一個網絡往返就可以展示,有效地規避了后續需要等待css/js資源加載、數據拉取的時間。性能體驗有比較大的提升,在BFF普及的情況下開發模式簡單,很受歡迎。公司內相關工作考慮到WebView的初始化(冷啟動/ 二次啟動)、頁面網絡請求、首屏數據接口的耗時,白屏時間還是可感知地存在的。以我們要開發的頁面為例采用SSR首屏耗時均值~600ms,可交互時間均值~1100ms。如何進一步消除白屏?這里為各位介紹公司內外針對h5首屏性能優化的優秀方案。手Q團隊的VasSonic是集大成者,主要思路是采用WebView和數據預拉取并行的方式。這套方案需要客戶端和服務端采用指定協議改造接入,開發時也有一定的改造工作。微信游戲團隊主要思路是利用jsCore做客戶端預渲染,用戶點擊后直接上屏。這個方法也達到了很好的效果,首屏FCP時間從1664ms降低到了411ms。我們做了一個簡要的方案對比,可以看到每個方案都針對渲染鏈路的某個或多個環節做了優化,其中VasSonic的效果比較顯著。不過結合企業微信業務實際情況,我們列出了如下幾點考慮:首先,接入對客戶端和服務端有一定的改造成本,業務開發也有一定的改造工作。其次,我們已經有一套的統一發布平臺,希望能復用這套發布能力。最后,性能上有沒有進一步優化的空間呢?業務需求對體驗上的要求是希望達到更好的性能效果或者說盡可能完全地消除白屏?;谝陨峡紤],我們在上述方案的基礎上做了進一步的實踐探索,以期望達到更好的性能效果。
離線包 | SSR | VasSonic | CSR |
資源加載 |
數據拉取 |
JS執行 |
WebView啟動優化 |
首屏FCP |
可交互(取決于JS執行) |
2)方案架構為了達到盡可能完全消除白屏,我們還是從初始問題出發,結合渲染鏈路進行分析,思路上針對每個環節采取對應的優化方法。每個環節的優化在具體落地時會存在著方案的利弊取舍。比如預拉取數據一般的思路是交給客戶端來做,但是存在著客戶端請求和h5請求兩套機制(鑒權、請求通道等方面)如何協調的問題。在渲染鏈路分析時,如果業務的js執行也貢獻了不少耗時,有沒有可能從通用基礎方案的角度來解決這個問題,同時也能減少業務對性能優化的關注?這是個值得各位思考探索的問題。具體的內容會在后面展開來說。如圖展示了方案的優化思路和主流程。方案使用離線包解決了資源請求耗時的問題,在這個基礎上通過耗時分析找到瓶頸環節,進一步采用預熱的思路進行優化提速,解決了WebView初始化、數據預拉取、js執行(app初始化)耗時的問題,最終達到了理想的性能體驗。圖1 上屏流程圖2 方案架構下面我們具體介紹下方案,包括:離線包技術、預熱提速和進一步的優化工作。離線包加速為了規避資源請求耗時,我們使用了離線包技術。離線包技術是比較成熟的方案,相關打包、發布拉取的方案這里不多說了,主要說下方案中一些設計上的考量。1)加載流程我們通過offid作為離線包應用的標識,fallback機制保證離線資源不可達時用戶也可以正常訪問頁面,通過離線包預拉取和異步檢測更新機制提高了離線包命中率,盡可能消除了網絡資源加載的耗時。2)fallback機制因為用戶網絡狀況的不確定性,離線包加載可能存在失敗的情況。為了保證可用性,我們確定了離線包加載不阻塞渲染的思路。當用戶點擊入口url,對應offid離線包在本地不存在時,會fallback請求現網頁面,同時異步加載離線包。所以我們針對離線包的打包結構,按照現網URL path來組織資源路徑。這樣客戶端請求攔截處理也會比較方便,不需要理解映射規則。當發現離線包不匹配資源時,放過請求透到現網即可。如圖展示了我們的離線包結構示例。3. 離線包生命周期為了提高離線包命中率,我們會配置一些時機(e.g.入口曝光)來預拉取離線包。離線包的更新機制:客戶端加載時根據offid檢測到本地離線包的存在,則直接使用拉起,同時啟動異步版本檢測和更新。如果新包版本號大于本地版本號則更新緩存,同時發布平臺也支持區分測試環境、正式環境以及按條件灰度。上了離線包后,可以看到頁面的首屏耗時均值從基準無優化的1340ms降到了963ms,離線包的預拉取和更新策略則使離線包命中率達到了95%。首屏耗時得到了一定的降低,但也還有比較大的優化空間,需要更一步的分析優化。預熱提速通過離線包的加速,我們解決了資源請求耗時的問題,不過從整個渲染鏈路來看還有很大的優化空間,我們做了具體的耗時分析,找出耗時瓶頸,針對耗時環節做了進一步的優化提速。1)耗時分析離線包技術規避了資源請求耗時,但是從整個渲染鏈路來看還有很大的優化空間,我們做了耗時分析如下。Hybird應用中,WebView初始化是比較耗時的環節,這里我們針對iOS WebView做了測試。
首次冷啟動/ms | 二次打開/ms |
iOS(WKWebView) | 480ms | 90ms |
數據拉取方面,不同入口頁面的耗時不一,某些入口頁面比較重的接口耗時超過了1s。此外,我們發現js執行也貢獻了不少耗時。以某入口頁面為例,框架初始化時間~10ms,app初始化時間~440ms。2)渲染鏈路預熱提速預熱流程我們的目標是消除白屏,這里理想的方案是找到一種和業務無關的通用解法。方案的主要思路是預熱,把能提前做的都做了。預熱是不是就是把WebView提前創建出來就好了呢?不是的,這里的預熱涉及到多個渲染環節的優化組合。如圖展示了預熱的整體流程,下面一個個來解。2)WebView預創建為了消除WebView的耗時,我們采取了全局的預創建WebView,時機為配置入口曝光。不過全局復用預熱WebView不可避免地會引入可能的業務內存泄露問題,下文會介紹對應的規避方案。數據預拉取數據拉取是頁面渲染的一個耗時環節。為了消除數據預拉取耗時,在預創建WebView階段我們同時進行了數據預拉取。數據預拉取常見的思路是交給客戶端來做,但是存在著客戶端請求和h5請求兩套機制如何協調的問題,以請求鑒權為例,存在以下的問題:第一,Web團隊自身有一層node BFF,實現了相應的數據拉取業務邏輯,而客戶端則走的私有協議通道請求C++后臺,二者是不同的鑒權機制。第二,如果交給客戶端來做,可以接入HTTP請求這套機制,改造成本比較大,如果復用原有通道,則一份數據業務邏輯需要兩套實現。如何設計一套通用可擴展的方案?我們希望做到客戶端只關注容器的能力(預熱、資源攔截等),屏蔽掉更深入的對Web的感知,這樣的解耦可以有效控制方案的復雜度。因此,這里我們針對離線包配置項增加了preUrl字段,使客戶端維護更通用的能力,數據預拉取交給業務團隊來做,具體如下:第一,客戶端:拉取某個離線包配置項時會讀取該字段,同時針對當前曝光的入口url可能存在多個有著不同的數據需求,這里會進行收集,將曝光url中的業務key參數拼接到preUrl來初始化WebView,這些作為通用能力。第二,業務:preUrl頁面在加載時會拉取相應的業務數據存到localStorage,實際的數據預拉取請求放到業務方發起,也可以很好地兼容已有的技術棧。JS預執行很接近目標了,最后js執行的耗時能不能消除呢?首先來看下440ms的耗時具體在哪里,通過分析看到,框架初始化僅需要不到10ms的時間,而真正的大頭在業務代碼的執行,其中代碼編譯耗時~80ms,其余的都是業務app初始化執行時間,這個是業務本身復雜度造成的。我們首先考慮了創建兩個WebView的方案,一個負責加載preUrl預拉取數據,另一個負責loadUrl上屏,這樣設計上比較簡潔健壯,不過實踐下來發現效果不理想,如圖展示了該方案的效果,渲染不穩定可以感知到白屏的存在。在已經有了預拉取數據和離線資源的情況下,理論上用戶點擊后需要等待的就只有渲染這塊的耗時,實際我們發現在復雜應用初始化時存在js執行耗時較大的問題。最終我們做了一個預執行的解法。結合SPA的特點,將preUrl作為SPA的一個子頁面,不需要UI展示,只負責預拉取數據,這樣子頁面加載完成的同時也完成了app提前初始化。而相應的不同入口切換頁面時,不同于復用預熱WebView重新reload頁面,為了保留app初始化的效果,我們采取了一套Native通知Web SDK,頁面切換交給WebView控制的方案。其中,Native通知則以調用SDK全局方法的方式。通過這種方式,入口頁面間切換其實只是hashchange觸發的子頁面渲染,達到了不錯的效果。流程圖即預熱方案的上屏部分。該方案執行后我們達到了預期目標效果,最大限度地消除了白屏接近Native體驗。需求上線后通過監控數據可以看到在命中預熱和離線包邏輯的情況下,從用戶點擊到頁面上屏可交互耗時均值約130ms。進一步優化1)離線包安全在離線包安全方面,為了防止包篡改,每我們次打包發布時都會生成包簽名和文件md5??蛻舳嗽谑褂媒馕鲭x線包時會校驗完整性,在返回離線資源時會校驗文件完整性。2)穩定性整體方案在性能上已經達到目標了,保證穩定性對產品體驗也很重要。我們為了消除js執行的耗時,采取了Native通知Web SDK控制頁面切換的方式。雖然比較靈活但是也帶來了穩定性的問題。具體來說,如果SDK在做頁面切換時異常,之后用戶打開每個入口url都會看到相同的頁面。入口頁面的業務在用戶使用過程中如果跳轉了非SPA的鏈接同時沒有注入SDK,之后的頁面切換也會失效。如何保證預熱容器的可用性呢?我們設計了一套通知機制確??蛻舳烁兄筋A熱容器的可用狀態,并在不可用時得以恢復,如圖。預熱容器會維護isInit和isInvokedSuc兩個狀態。只有當preUrl成功加載和SDK執行成功上屏時,兩個狀態才會置true,此時的預熱WebView才是可用的,否則會回退到普通容器模式進行load url來加載頁面。此外,在每次入口url曝光時,已有的預熱容器也會銷毀重建,也有效保證了容器的穩定性。3)內存泄露使用全局的預創建WebView,不可避免的會引入可能的業務內存泄露問題。在測試過程中,我們也發現了這種例子??梢钥吹疆旤c開使用了預熱容器的頁面后放置一段時間,整個內存在不斷上漲,最終會導致PC端頁面的白屏或者移動端的Crash,這個狀況最終歸因是業務邏輯的實現存在缺陷。不過在基礎技術的角度而言,開發者也需要采取措施來盡可能規避內存泄露的情況。主要思路是減少同一個預熱容器的常駐,也就是對存活的容器設置有效期,在適當的時機檢查并清理過期容器,我們選擇的時機是App前后臺切換時。4)解決副作用出于性能考慮,我們選擇了通過Web SDK控制頁面的方案,同時使用了全局的預創建WebView。這帶來了副作用——當頁面對容器做了全局的設置,可能會影響到下一個頁面的表現。比如:設置document.title、通過私有JSAPI設置了WebView導航欄的表......當執行這些操作時,在下一個頁面也復用預熱容器的情況下,全局設置沒有得到清理重置或者覆蓋,用戶會看到上個頁面的表現。為了解決上述問題,業務可以在每個頁面主動聲明需要的表現來覆蓋上個頁面的設置,理想的方法還是基礎技術來規避這個問題來保證業務開發的一致性。我們在SDK控制切換頁面時,進行了一系列的重置操作。此外,在Windows和Mac端,我們也設計了雙預熱WebView的方案來完全解決這個問題。每次使用時同時創建新容器,得以保證每次打開入口頁面都是使用新創建的容器。當然,方案的另一面則是會帶來App內存的上漲??偨Y我們從渲染鏈路入手,針對每個環節進行分析優化,最終沉淀了一套可用可擴展的Hybird H5秒開方案。從渲染鏈路的角度來看,方案通過離線包和預熱一系列優化,將用戶從點擊到可交互的時間縮短到了一個SPA路由切換上屏步驟的耗時。上線后我們監控發現,命中了預熱離線邏輯的頁面首屏耗時~130ms,相比于離線包、SSR都有優勢,同時預熱離線容器命中率也達到了97%,達到了理想的體驗效果。希望本篇對你有幫助。??
騰訊工程師技術干貨直達:
1、算法工程師深度解構ChatGPT技術
2、10分鐘!從架構視角讀懂K8s
3、探秘微信業務優化:DDD從入門到實踐
4、耗時減半?騰訊云OCR只做了3件事
關鍵詞:
的情況下
進行分析