武漢最全辦證一天搞定、辦畢業證指南,拿走不謝!
武漢辦證〖薇Q:9560-52852】 無.需.點.開/長.期.有.效!辦理文憑學歷畢業證-學位證-教師證-離婚證-四級成績單-計算機等級證-各類證C++專.業.辦.理-質.量.上.乘-
作者:billpchen,騰訊看點前端開發工程師
近兩年,資訊流行業處於一個增長緩慢甚至停滯的狀態,騰訊看點一直在尋求自己的破局之路。研究發現,近兩年實現爆發增長的業務都具有使用者覆蓋面大、差異化小的普適特點。
什麼內容具備普適特點呢?有兩類,一類是打發時間、放鬆解壓的搞笑內容,一類是明星八卦、話題談資的熱點內容,這兩類內容具有低門檻、 快消費、易傳播的特點。為了進一步降低內容消費的門檻,我們把消費場景放在了資訊流中,使用者無需進入詳情頁就可以直接消費完文字、圖片、動圖、視訊等內容,這種新的內容形態被稱為“短內容”,由短內容構成的資訊流被稱為“短內容頁面”。
QQ 瀏覽器中短內容頁面的入口是在推薦流中的短內容卡片,一般帶有分享、評論、點贊互動欄的就是短內容卡片,點選短內容即可以進入短內容頁面。
對於 C 端頁面,使用者體驗尤為重要,尤其是首屏體驗,更是奠定了使用者的第一印象,所以“效能優化/首屏優化”常作為前端人的重要研究課題。那短內容頁面的首屏體驗是怎麼樣的呢?
看完上面在手機效能相對較好的 iPhone X 上的演示動圖,你一定會感覺到這真是個糟糕的體驗,那到底糟糕在哪裡呢?具體有三點:白屏時間長、圖片載入慢、頁面過渡僵硬。本地的首屏優化方案就集中在這三方面,本文也圍繞這三點詳細闡述。
1. 白屏時間長
所謂白屏,指開啟新頁面時螢幕中沒有任何有意義的內容,只有無休無止令人窒息的空白。造成頁面白屏的原因有不少,比如頁面崩潰、網路資源載入較慢、頁面啟動卡頓等等,這裡不討論頁面崩潰等程式出現 bug 的場景,有 bug 就去解決嘛。
我們都討厭白屏,有時白屏時間比較短,在我們的容忍範圍內,但有時白屏時間很長,那就令人煩躁了。據統計,大多數使用者可以忍受 1000ms 以內的白屏時間,超過 1000ms 時隨時間的變長越來越無法忍受。所以我們的首屏優化目標是 1000ms 以內,即“秒開”。
定好了目標,下面貌似就應該行動起來尋找解決方案了,但等等,我們一直在說“首屏”,那什麼是首屏,我們得先嘮嘮。首屏的英文名是 “Above the Fold”,Fold?跟摺疊有什麼關係?“Above the Fold” 這個概念最早用於出版行業,買過報紙的同學都知道,因為方便搬運,報紙的一般都是摺疊起來的,即使是“頭版”,也分朝上和朝下的一面,如圖所示,朝上的那一半被稱為 “Above the Fold”,也是被報社認為最重要的位置。延伸到網際網路產品,“Above the Fold” 用來指代頁面不用操作(比如點選、滾動)就能看到的資訊。
明白了“首屏”,那什麼是“首屏時長”?雖然“首屏”這個概念是從報紙那裡借鑑過來的,但我們看到報紙的頭版也就看到了,不存在先看到一部分再看到完整的,所以“首屏時長”是屬於網際網路產品特有的概念。為了衡量看到“首屏”內容的效率,人們定義了很多標準,比如 Google 就定了 FP、FCP、FMP 等很多指標來衡量首屏的效能,久而久之,這些標準成了大家公認的標準。短內容頁面基於 Hippy,一種動態化框架,本身沒有什麼衡量標準,所以我們就仿照 Chrome,定義了動態化頁面的首屏開啟效能指標:FCP 和 FMP。
在我們的定義中,短內容頁面的 FCP 指的是從外層入口點選的時間點到短內容頁面根元素 didMount 的時間點的差值,FMP 指的是從外層入口點選的時間點到短內容第一條卡片根元素 didMount 的時間點的差值。這裡的 FCP 和 FMP 可能跟 Chrome 中的定義有所偏差,不過無傷大雅,我們需要的只是有那麼幾個指標來衡量優化前後的效果。
明確了首屏時長的衡量標準後,那下面我們就可以開始正式的優化環節了。從哪裡下手呢?既然要縮減白屏時長,那就要了解首屏載入有哪些環節,辨別出關鍵的耗時環節才能有的放矢。
如圖所示,當用戶點選了短內容頁面的入口時,客戶端開始建立 Activity,然後開始建立 Hippy 引擎,引擎建立完成後載入 Bundle,而後向前端傳送 loadInstance 事件開始啟動業務,接著便開始渲染 HippyRootView,下面的事前端就比較熟悉了,拉取資料,渲染內容。
整個過程可以分為兩個部分:頁面啟動和資料載入,我們分別從這兩方面進行優化。
1.1 頁面啟動
頁面啟動階段主要有 initEngine、onInitialized、loadModule 和 loadInstance 4 個階段,逐個分析發現,在“載入 bundle” 階段我們可以有所作為。載入 bundle 的時間跟 bundle 體積成正相關,如果我們把 bundle 的體積減小,那麼 bundle 自然載入地更快。如何減包呢?我們可以使用 Webpack Bundle Analyzer 對 bundle 進行分析。
從分析結果中,我們可以看到有近 10 個檔案的體積超過了 100 KiB,還有很多的檔案達到數十 KiB,那開始挨個分析每一個檔案。雖然 CircleCommJce.js 體積最大,但專案處於新舊交替過程中,專案原先是通過 WUP 協議拉取資料,而現在隨著騰訊看點三端(QQ 看點、QQ 瀏覽器、看點快報)的統一,CGI 服務也統一成 HTTP,所以這裡逐漸會將 WUP 協議換成 HTTP 協議,等專案協議切換完成,這些與 WUP 相關的檔案都可以刪掉了。與 CircleCommJce.js 類似的還有 MTT4PageInfoJce.js 等等,這些檔案我們暫時不作改動。
接著,lodash.js 這個檔案引起了我的注意。Lodash 是一個便捷的 JS 工具庫,但便捷的代價就是專案體積的增加,這不,lodash.js 就有 466 KiB。那如何減少這個檔案的體積呢?很簡單,徹底不用。在專案中檢索 “lodash”,發現被引用了 19 次,共 45 次呼叫。乍一看還挺多,仔細梳理,發現一共就用到了 Lodash 的 5 個方法:_.get、_.chunk、_.pick、_.pickBy 和 _.mapValues。將這些方法用自己寫的 JS 函式替代,比如下面就是 _.get 的簡單實現。
將lodash.js
刪掉後,打包後的 bundle 體積減少了 2.4%,安卓的啟動時長減少了 5.2%,iOS 的啟動時長減少了 3.9%。
1.2 資料載入
資料載入階段主要包含 fetch 和 setContentView 兩個階段,逐個分析發現,主要的耗時環節是獲取資料環節,那如何優化這部分的耗時呢?我們再次細化獲取資料環節,大概分為 DNS - 建立連線 - 後臺處理 - 個性化推薦 - 資料返回這幾個階段。網路傳輸環節與運營商相關,個性化推薦與演算法、機器數量和效能相關,即便優化,耗時也很難有實質性的縮減。總而言之,從減少獲取資料耗時這一環節本身出發,我們做不了什麼。那是否意味著這個環節我們不能優化了呢?方法還是有的,我們可以提前獲取資料,然後快取到本地,等使用者開啟頁面的時候直接從快取獲取第一刷的資料。從快取讀取資料會比從網路讀取資料減少不少的耗時,那具體怎麼做呢?
短內容頁面的入口是推薦流中的短內容卡片,當推薦流中有短內容卡片曝光時,QQ 瀏覽器將會去網路拉取一刷資料快取到本地。如果使用者真的點選了短內容卡片進入了短內容頁面,那麼會直接從快取中獲取資料。這個方案被稱為“資料預載入”。
道理我都懂,但感覺太簡單,裡面會不會有坑呢?有的,資料預載入可能存在四個方面的問題。
-
流量浪費
如果使用者沒有進入短內容浮層,那這部分流量不是浪費了嗎?這裡要分兩方面看。一方面是公司的流量浪費,公司的流量費用是與公司峰值頻寬相關的,資料預載入對公司的峰值頻寬影響不大,所以不會導致公司方面的流量浪費;另一方面是使用者的流量浪費,據統計有 44% 的短內容使用者用流量瀏覽,對於這部分使用者來說,預載入方案是一種以空間換時間的方式,這部分的流量浪費不可避免,並且為了減少流量浪費,我們選擇了預載入 3 條而不是更多,因為一般情況下 3 條恰好可以覆蓋一屏,包含 1 條主 TL 的帖子和 2 條推薦帖子。 -
推薦浪費
使用者看到的帖子都是由推薦系統推薦的,已經看過的帖子推薦系統就不會再給我們推送了,這叫“曝光去重”。在資料預載入場景下,很可能會出現推薦系統推薦了帖子,但使用者實際上沒有進入短內容頁面也就沒有消費這些帖子的情況,這叫做“推薦物料浪費”。針對這種情況,我們與推薦後臺約定,預加載出的推薦資料在下一次推薦時不會被曝光去重,只有使用者真正消費的時候,前端回寫曝光資料,告訴推薦後臺哪些帖子被真正消費了,那麼這些帖子才會被曝光去重,那麼這樣也就避免了推薦物料被浪費的情況。 -
快取關閉
預載入的資料會被瀏覽器快取在記憶體中,當瀏覽器執行在前臺時,手機分配的記憶體空間足夠;而當瀏覽器切到後臺時,手機分配的記憶體空間減少,會導致預載入資料的快取空間被清除,這樣不僅之前快取的預載入資料都被清除,下一次寫快取資料時也會失敗。為了解決這個問題,只要每次寫快取時檢查一下快取空間還在不在,在的話就就直接寫,不在的話就得重新建立快取空間。 -
二次開啟
當用戶退出短內容頁面時,大約會有 3% 的使用者會重新進入。第一次開啟短內容頁面的時候,推薦的 2 條資料會被清除,快取中只會留下第一條資料。使用者第二次進入的時候,只會讀取那一條快取的內容,其餘的需要從網路拉取。
1.3 Bundle 預載入
分析了首屏渲染的各個關鍵耗時環節,我們縮減了 bundle 的體積,提升了頁面啟動的速度;同時對資料進行了預載入,第一刷的資料直接從快取中讀取,提升了資料拉取速度。除此以外,還有其他優化方法嗎?減包加速頁面啟動終究還是有瓶頸,何不一步到位直接讓頁面執行在後臺,等到使用者真正點選的時候再把頁面提升到前臺呢?
這個方案的原理跟手機中的 APP 很相似,如果一個 APP 執行在後臺,那被切到前臺時將會很快,但如果被第一次開啟,耗時將會較長。所謂的 bundle 預載入,就是將短內容頁面預先載入在瀏覽器的後臺,等到使用者點選開啟頁面時再顯示出來。這一過程的演示:
視訊中透明的浮層代表沒有任何內容的短內容頁面,當瀏覽器啟動時,會在背後悄悄啟動一個空白的短內容頁面,如果使用者點選了入口短內容卡片,那麼這個空白的短內容頁面將會被提升到瀏覽器的最頂層,並且被渲染。這樣,bundle 載入的耗時將會被大大縮減。除此以外,瀏覽器還會在後臺又啟動一個空白的短內容頁面,以備下一次使用者開啟短內容頁面使用。
1.4 小結
為了解決白屏時間長的問題,我們仔細剖析了頁面載入中的每一個環節,其中針對“載入 bundle”和“獲取資料”兩個關鍵耗時環節採取了“減包”和“資料預載入”措施,同時也認識到“減包”所帶來的收益是遞減並且有瓶頸的,所以直接採用“bundle 預載入”的方式,在瀏覽器啟動時準備一個執行在後臺的空白短內容頁面,使用者開啟時直接使用該空白頁面,大大縮減了頁面啟動了時間。
2. 圖片載入慢
從網路載入圖片資源需要一定的耗時,所以時常會出現文字已經展示但圖片還是一片灰色的情形。那如何縮減圖片的載入時長呢?我們探索出了 4 個方案:圖片壓縮、圖片裁剪、SharpP 和圖片預載入。