QQ音樂 Android 客戶端的 Web 頁面日均 PV 達到千萬量級,然而頁面的打開耗時與 Native 頁面相距甚遠,須要系統性優化。本文將介紹 QQ 音樂 Android 客戶端在進行 Web 頁面通用性能優化過程當中的問題、思路、方案和效果,並嘗試對跨端場景的常見瓶頸和對策進行概括。文章做者:關岳,QQ音樂客戶端開發工程師。前端
做爲一款注重於內容運營的應用程序,QQ 音樂 Android 客戶端的 Web 頁面日均 PV 達到千萬量級,評論頁、MV 頁等核心頁面均有 Web 頁面參與,或徹底由 Web 實現。web
客戶端內 Web 頁面的打開耗時與 Native 頁面相距甚遠,須要系統性優化。然而,現有的前端和跨端優化方案,存在必定侷限性。小程序
針對 Web 頁面的耗時優化,在優化思路、方案、服務、工具鏈等方面都已經建設得很是詳細。然而,在客戶端內 Web 頁面這一場景,純前端優化存在如下兩個侷限:微信小程序
沒法規避 WebView 初始化耗時緩存
受限於 WebView 生命週期範圍性能優化
從客戶端角度,除了思考優化 WebView 初始化耗時以外,還能夠從 「擴展前端生命週期」 的角度出發,思考優化方案。微信
現有跨端優化方案,包括離線包、VasSonic 等,爲了達到最好的優化效果,均須要前端終端共同參與改造。這致使存量頁面的邏輯改造增長,對線上頁面不夠友好,引入額外的成本和風險。在前端開發資源不足時,這些優化的開展存在必定難度。框架
從減小前端開發工做量的角度來看,須要思考更具通用性、前端感知更小的優化方案。dom
基於本次優化的背景,本次優化提出如下兩方面的目標:加強通用性、減小前端改形成本。前端優化
在展開優化思路和實施的同時,須要創建衡量優化效果的性能指標。
接下來基於客戶端內 Web 頁面加載過程,描述客戶端現有性能指標表明的時機。
基於 Android WebView 的過程監控回調和頁面框架能力,能夠實現的性能監控包括:
其中,
onMainFrameFinished
取第一個非主請求 (HTML) 的資源被攔截的時機。對於絕大多數頁面來講,此時已經完成主請求 (HTML) 的下載,並已經開始解析;能夠粗略表明主請求流程結束。
與客戶端回調相比,W3C Performance Timing 提供了更細緻的加載過程信息,可是不包含 WebView 開始初始化的時間點。下圖中僅列出部分:
沒法獨立獲取 WebView 開始初始化的時間點。
想獲取最精確的加載完成時間點,主要依賴手動埋點。
SSR (服務端渲染) 和 CSR (客戶端渲染),頁面內容可消費的時間點不一致。
對 WebView 頁面加載週期來講:
CSR 頁面需在前端頁面框架加載後再展現數據,內容請求完成並上屏,發生在頁面加載完成以後
SSR 頁面的首次內容上屏可攜帶首屏數據,所以在頁面加載完成以前,頁面內容已經能夠被消費
客戶端回調時機不夠完整或過於「苛刻」,測不許「頁面內容可消費」的時間點。
經過追溯客戶端 onPageFinished 的回調時機,發現對應的 Blink 代碼要求必須知足:頁面解析完畢、 沒有正在下載的資源等條件。
按照這個標準,一旦存在某個圖片一直處在加載中,但頁面框架的其餘內容均已處理完畢,onPageFinished 回調也會等待圖片加載完成纔回調,與實際上的 「頁面內容可消費」 時間點存在差別。
結合上述分析,能夠肯定:
最準確的頁面加載完成時機來自前端
最準確的 WebView 初始化時機來自客戶端
所以,完善的耗時測量需由客戶端和前端協同完成。
前端自行完成結束時間點的設置,並從客戶端獲取 WebView 初始化時間點,統計上報打開耗時。
前端經過手動埋點或監聽 DOM 節點數變動,獲取加載完成時間點。
前端統計時調用客戶端提供 JSAPI,獲取以 WebView 初始化時間點做爲起點的耗時。
並由前端完成加載耗時的計算和統計上報。
做爲一個補充方案,客戶端能夠經過 JavaScript 注入獲取上述 W3C Performance Timing 中的 domInteractive 時間點,做爲結束時間點。
前端
domInteractive
時,已完成全部頁面展現必需資源的請求和處理耗時的差別,能夠體現任何頁面的客戶端通用優化效果
能夠衡量SSR(服務端渲染) 頁面的可消費耗時,和CSR(客戶端渲染)頁面的首幀耗時
webView.evaluateJavascript(
script = 「(function(){return performance.timing.domInteractive;})();」,
callback = { value ->
responseEndDuration = value.toLong() - getOnCreateTimestamp()
}
)
雖然 WebKit 負責維護 Performance Timing 的值,可是 WebView 並未提供接口獲取上述時間點的值。
基於客戶端內 Web 頁面的加載流程,從 「WebView 初始化耗時優化」、「資源加載耗時優化」、「邏輯處理耗時優化」 三個方面,提出了 5 個優化項。
TBS (X5 內核) 環境預加載
WebView 實例池
主請求並行加載
Web 公共資源池
跟膚邏輯優化
各優化項在 Web 頁面加載過程當中的生效時機以下:
通過前期分析,WebView 初始化耗時自己的耗時壓縮空間比較有限。所以優化手段主要以初始化邏輯前置爲主。例如,「WebView 實例池」 經過在應用位於後臺、主線程卡頓影響不明顯的時機進行 WebView 預初始化,置換啓動 Web 頁面時的初始化耗時。
爲了實現前述各項資源加載優化,客戶端須要獨立於 WebView 的緩存機制,自建一個資源緩存。
自建緩存參考客戶端經常使用的三級緩存機制,基於 WebView 的強生命週期,設計了 「冷-熱緩存循環」 的緩存生命週期。
例如,在 WebView 初始化的同時,自建緩存把頁面須要的資源從文件系統加載到內存;向 WebView 資源攔截回調輸入字節流時,自建緩存必定從內存緩存中輸出,輸出完畢後便可當即從內存緩存中被清除。這一機制可使內存緩存的淘汰更積極,字節流在內存中停留的時間更短,減小內存佔用。
在完成公共資源池開發後,頁面打開耗時出現了負優化的狀況。通過分析,肯定與資源攔截回調的性能瓶頸有關。
單線程模型致使讀寫性能降低
被攔截資源的數量越多,對性能的影響越容易被放大
所以,爲了減小資源攔截回調的性能影響,從減小攔截次數的角度,引入了公共資源內聯優化。
公共資源加載到熱緩存後,轉換爲對應的 HTML 節點
主請求並行加載完成後,直接在主請求字節流中替換其對應的外聯節點;替換後的新字節流返回 WebView
引入公共資源內聯後,基本抵消了資源攔截回調的性能影響,頁面加載耗時提高 3.2%。
QQ 音樂 Android 端內評論頁:
加載耗時下降 26.2% (1932ms → 1426ms)
跳出率下降
停留時長中位數增長
基於在 WebView 場景下的優化過程,推及跨端場景可能存在的相似問題,本文嘗試給出一些跨端場景中可能的性能瓶頸及應對方式。
跨平臺方案 (WebView、React Native 等) 廣泛存在前終端通訊通道效能不足的問題。
WebView 通道不支持較大量級數據的傳遞
通訊線程多爲單線程,甚至須要在主線程發起或處理通訊
對傳遞次數的敏感程度大於對傳遞數據總量的敏感程度
所以,當在跨端場景出現大數據量傳遞時,須要優先考慮當前通訊通道的可用性。在須要傳遞數據總量沒法壓縮的狀況下,若是通道容許,儘可能減小傳遞次數,增長單次傳遞的數據量。
「公共資源內聯」 便是這一思路的實踐。
前端生命週期有限。客戶端能夠利用在前端生命週期之外的時間,進行適當的資源前置和邏輯前置,下降頁面加載耗時。
例如上述優化中的 「公共資源池」、「主請求並行加載」 等,體現了擴展生命週期的思想。除此以外,微信小程序的雙線程模型[1]經過引入 JSCore,增長前端代碼的可執行時長,並經過離線包等手段幫助前端擴展生命週期。
若是前端頁面共用公共庫,隨着前端業務的複雜化,公共庫的天然膨脹,可能會放大腳本解析與執行的耗時。
針對 Web 頁面,能夠經過精簡基礎庫的方式,減小無關代碼的執行;針對 React Native 頁面,能夠經過進行分包和實例預加載,讓更多基礎庫代碼在頁面加載前執行,從而下降頁面啓動時執行的代碼量,減小耗時。
本文基於客戶端內 Web 頁面的加載特色,針對 WebView 初始化、資源加載和邏輯處理現狀中的問題和瓶頸,設計並實施了 5 個優化項,優化效果比較明顯。而且嘗試對跨端場景的瓶頸與對策進行概括,嘗試爲後續跨端場景的優化工做提供思路。
將來,團隊還將進一步豐富客戶端與前端的協同性能監控,並容許前端經過更精細化的方式啓動客戶端 Web 頁面框架。遠期,還將嘗試探索 CGI 前置、引入 JSCore 等手段,進一步提高特定場景下的 Web 頁面加載耗時。
參考資料:
[1] 微信小程序的雙線程模型:
https://developers.weixin.qq.com/ebook?action=get_post_info&docid=0000286f908988db00866b85f5640a
看騰訊技術,學雲計算知識,關注雲加社區