白屏优化_今日头条品质优化 图文详情页秒开实践
背景
作為一個內容類應用,看新聞讀資訊一直是頭條用戶的核心需求,頁面的打開速度直接關系到用戶使用頭條的核心體驗,在頭條中,為了更多的承載足夠豐富的樣式和邏輯下保持多端體驗的統一,詳情頁的內容我們是通過 WebView 來承載的,但 WebView 本身的性能相比 Native 來說比較差,因此,今日頭條技術團隊一直致力于優化詳情頁的加載速度。
經過不斷的優化,目前今日頭條中詳情頁在線上的打開體驗,從肉眼上基本已經感知不到加載過程。在接下來這篇文章里,我們會逐步拆解和介紹我們對詳情頁加載優化的思路和實踐。
先讓我們來看看優化前后的效果吧~
今日頭條詳情頁加載體驗優化前今日頭條詳情頁加載體驗優化后數據建立
性能
當我們開始著手優化頁面加載速度之前,我們需要明確一個問題,怎樣才是用戶真正體驗到的頁面加載時間。
首先我們可以看下面這個公式:
頁面加載時間?=?頁面加載完成時間?-?頁面開始加載時間頁面開始加載時間很好確定,當用戶點擊了 Feed 上的卡片,我們就可以認為頁面開始加載了。
問題是怎么定義頁面加載完成了呢?從客戶端的角度上看,無論是 iOS 還是 Android,WebView 都提供了一個 loadFinsih 的回調,但在實際應用中我們發現,loadFinish 回調并不能反應用戶的真實體驗。
一般來說,WebView 渲染需要經過下面幾個步驟
而 loadFinish 實際上是在頁面加載完畢階段,而 DOM 構建完成時頁面結構就已經基本渲染完成,所以從用戶真實體驗的角度出發,我們以 DOM 結構構建完成(即 domReady)的時間點作為頁面加載完成時間點。
白屏
在詳情頁瀏覽過程中,除了頁面加載速度之外,還有一個特別影響用戶體驗的問題,就是頁面的白屏,也是早期的時候用戶反饋比較多的問題,但有很多場景都可能導致詳情頁發生白屏,比如說網絡異常,WebView 異常等等,需要從用戶體驗的角度出發去檢測用戶發生白屏的情況。
目前可以想到最直觀的方案就是對 WebView 進行截圖,遍歷截圖的像素點的顏色值,如果非白屏顏色的顏色點超過一定的閾值,就可以認為不是白屏,目前需要考慮的是這個方案的性能問題和檢測時機。
iOS 中提供了 WebView 快照的接口獲取當前 WebView 渲染的內容,底層采用異步回調的實現方式,API 耗時 10ms 左右,用戶基本無感知。
-?(void)takeSnapshotWithConfiguration:(nullable?WKSnapshotConfiguration?*)snapshotConfiguration?completionHandler:(void?(^)(UIImage?*?_Nullable?snapshotImage,?NSError?*?_Nullable?error))completionHandler?API_AVAILABLE(ios(11.0));Android 中系統提供的獲取視圖內容的接口為 getDrawingCache,API 耗時在 40ms 左右,性能損耗也不是特別大。
除了截圖的性能損耗,像素點檢測也是白屏檢測中比較耗時的場景,經過實驗,我們把 WebView 截圖的圖片進行縮小到原圖的 1/6,遍歷檢測圖片的像素點,當非白色的像素點大于 5% 的時候我們就認為是非白屏的情況,可以相對高效檢測準確得出詳情頁是否發生了白屏。
指標建立
確定好口徑之后,我們還有需要明確的一個問題是,什么指標可以反映用戶刷頭條時的真實體驗。
最早的時候,我們用的是詳情頁頁面的頁面平均加載時長,也就是頁面加載時長的總和/頁面 pv,在開始的時候這個指標也的確可以明確我們的加載速度。
后來隨著詳情頁的加載優化逐漸的深入,會發現平均加載時長雖然也可以反映詳情頁加載速度,但是因為詳情頁的 pv 比較高,如果使用平均加載速度化很多用戶體驗問題就被平均掉了,并不能反映用戶的真實情況,后面我們又調整了口徑,將指標調整為所有用戶進入詳情頁的 80 分位值,比如說,假如頭條詳情頁加載速度 80 分位值是 1 秒,那么就說明 80% 的情況下用戶進入詳情頁都能在 1s 內加載完成,當然經過我們的不斷優化,詳情頁加載的 80分位值已經能夠達到 0.3s 以內,也就是說,80% 的情況下用戶都能夠在?0.3s 內完成頁面加載。
80分位優化數據對比再后來我們又發現,在頭條詳情頁的量級下面,即使是 80 分位的數據也不能反應許多長尾用戶的真實情況,也為了更極致的追求詳情頁的加載性能,我們最后將詳情頁的性能口徑調整到 95 分位。到目前在我們的努力下,今日頭條詳情頁的加載速度 95 分位也優化了將近 80% 。
我們究竟做了什么呢,接下來會慢慢介紹一下。
模板優化
模板拆分
如前所述,圖文詳情頁是通過 WebView 來承載的,而 WebView 承載頁面最簡單的做法就是直接通過 URL 去加載一個線上頁面。那么先來一道簡單的面試題,當用戶從瀏覽器輸入一個 URL 到頁面展現發生了什么呢?
之前已經介紹過頁面的渲染流程了,現在我們再簡單看看用戶從點擊到看到頁面內容需要經歷如下幾個階段:
WebView 加載流程可以看到,通過線上頁面加載用戶每次進入詳情頁都要通過多次網絡加載,極容易受網絡波動的影響,這種情況下,也無法保證頁面加載的時長和成功率,極大的影響了用戶體驗。
于是在頭條中,我們將新聞中標題和正文內容進行拆分,把頭條詳情頁的公共樣式 CSS 和 邏輯 JS 都抽離出來,形成一個獨立而完備的詳情頁模板,這樣我們就可以把模板直接內置在客戶端中。
同時我們會與前端約定好的 JS 腳本,通過接口將正文內容數據注入頁面完成詳情頁的頁面展示,通過該這種方式我們可以將接口放到客戶端上進行請求。
這樣用戶進入詳情頁的時候只需要本地加載模板,而且加載模板的時候也可以同時并行請求詳情頁數據,再將數據注入進模板中。
那么用戶點擊到看到頁面內容只需要經歷下面的階段:
模板拆分如上圖所示,我們只需要通過一次網絡加載就可以完成頁面渲染。
還能不能更快一點呢?當然能!
為了提高頁面的加載速度,客戶端通過一定的策略去預加載新聞數據,這樣在理想狀態下用戶進入頁面時看到頁面時就可以直接使用緩存的數據,用戶在看新聞的時候可以實現完全離線化,避免受到網絡的影響。
本地加載模板預熱
完全脫離了網絡加載之后,還能再快一點呢?當然還是可以的!
當全流程離線化之后,頁面加載的瓶頸就變成了本地模板的加載時間,所以我們接下來要做的就是優化模板加載時間。
對于模板來說,我們做了兩件事情
模板合并,正常來說,WebView 需要在加載完主 HTML 之后再去加載 HTML 中的 JS 和 CSS,需要多次 IO 操作,于是我們將 JS 和 CSS 還有一些圖片都內聯到一個文件中,這樣,加載模板時就只需要一次 IO 操作,也大大減少因為 IO 加載沖突導致模板加載失敗問題
模板簡化,我們將部分非必須的腳本異步化拉取,精簡不必要的樣式和 JS 代碼,將模板大小壓縮了 20% 以上
通過上面優化,我們就已經將模板加載時間大大優化了,但是還能不能更給力呢?還是可以的。
對于客戶端來說,當模板跟數據分離之后,由于每次用戶點擊的時候加載的都是同一個模板,所以實際上,我們并不需要在用戶進入頁面的時候才去創建 WebView 以及加載模板,我們只需要在合適的時機在后臺創建 WebView,并且提前預熱加載模板,當用戶點擊進入頁面的時候就能使用已經加載好模板的 WebView,直接將詳情頁的內容數據通過 JS 注入到頁面中,前端收到數據后進行頁面渲染即可。
此時用戶進入詳情頁實際就不再需要重新加載模板了,路徑就變成了:
模板預熱可以看下,通過本地測試的模板預熱和數據預取的優化效果,還是比較明顯的,基本上已經達到了上面的截圖中的驗證效果。
本地測試數據模板復用
當我們拆分完模板和數據之后,數據上優化已經比較明顯,但我們說過,除了驗證數據,我們還需要看線上用戶的真實體驗數據,從 95 分位上看實際數據優化卻不是很明顯,所以我們從數據上觀察,用戶預熱模板的命中率只有 53%,還有進一步的提升空間。
模板預熱率為了盡可能的提高頁面的加載速度,我們希望用戶每次進入詳情頁的時候都能夠使用預熱好模板的 WebView,一般情況下,我們都會使用模板預創建池的手段來優化用戶進入詳情頁時的預熱模板命中率。
但其實在很多情況下,WebView 的創建是一個性能開銷比較大的操作,如果我們使用預創建池的方案,那么就會在后臺頻繁創建 WebView,這樣對用戶在 Feed 場景的瀏覽體驗也會有一定的影響。
而且假如用戶頻繁且快速進出詳情頁時,實際場景中用戶也很容易遇到無法命中預熱模板的場景。
這個時候為了優化用戶的體驗,如前文所述,我們每次使用的時候都是同一個模板,所以我們使用完當前 WebView 之后,只需要在用戶退出頁面的時候把正文數據清空,這樣進入下一個頁面的時候就能夠繼續復用這個 WebView 重新注入數據即可。
通過這個手段,我們既避免了頻繁在后臺預創建 WebView 對用戶刷 Feed 體驗的影響,把用戶進入頁面時候的預熱模板命中率從 53% 提升到 92%,優化了用戶體驗。
預熱模板命中率網絡優化
說完我們在模板 WebView 方面的優化之后,再介紹一下我們在內容請求上的優化。
CDN 加速
由于頭條詳情頁請求有以下特點
所以我們將詳情頁內容數據分為靜態和動態兩部分,將正文內容、標題、作者欄等用戶主要消費的又基本不變的內容托管到了 CDN 上。
CDN 的全稱是 Content Delivery Network,即內容分發網絡。其目的是通過在現有的Internet中增加一層新的網絡架構,將網站的內容發布到最接近用戶的網絡“邊緣”,使用戶可以就近取得所需的內容,提高用戶訪問網站的響應速度。CDN 有別于鏡像,因為它比鏡像更智能,或者可以做這樣一個比喻:CDN = 更智能的鏡像 + 緩存 + 流量導流。因而,CDN 可以明顯提高Internet網絡中信息流動的效率。從技術上全面解決由于網絡帶寬小、用戶訪問量大、網點分布不均等問題,提高用戶訪問網站的響應速度。
托管到 CDN 之后,全國各地的用戶可以直接從最佳節點就獲取到詳情頁數據,也大大節省了帶寬成本。
容災
1. 多域名備份
為了防止某個 CDN 出現故障,導致服務雪崩,服務端會下發多個 CDN 鏈接,當用戶訪問當前 CDN 節點的出異常時,可以快速自動切換到下個 CDN 節點。
2. 快速超時
一般的超時策略,客戶端在請求時,會遍歷請求 CDN 1、2、3。如果這些 CDN 都請求失敗,則整個網絡請求算作失敗。
但這個方案的問題是,假設請求 CDN 的超時時間是 15s。如果 CDN 1 出現故障,則需要等待 15s 才能切換到 CDN 2上,這對于詳情頁的加載時間來說是不可接受,如果用戶網絡突然變差,則需要等待 45s 才能返回失敗展示錯誤頁。
基于此我們設計了詳情頁請求的快速動態超時策略
單次請求 CDN 的超時時間,根據上次成功請求 CDN 的值計算,因子 1.5(z值)。且最小為 1s(x值),最大為 4s(y值)。超過這一時間不取消,直接請求下個 CDN。
單次請求 CDN 有一個硬性超時時間 4s(w值,w需>=y),超過這一時間請求取消。n 個 CDN 的請求全部取消后反饋用戶失敗。
幾個 case:
- 第1個 CDN 突然掛掉(假設上次成功請求的耗時為a) 下一次請求:第一個 CDN 很快超時(a * 1.5);開始請求第二個 CDN(超時時間為 a * 1.5,但實際上 b 秒就會返回請求)。用戶本次等待時間為 a * 1.5 + b 下兩次請求:第一個 CDN 很快超時(b * 1.5);開始請求第二個 CDN(超時時間為 b * 1.5,但實際 c 秒就會返回請求)。用戶本次等待時間為 b * 1.5 + c
- 用戶突然進入了一個網絡很差的環境(假設上次成功請求的耗時為a) 下一次請求:第一個CDN很快超時(a * 1.5);開始請求第二個 CDN(a * 1.5)也超時;開始請求第三個 CDN(a * 1.5)。最后一個請求會在 a * 3 + w 后返回失敗(這個值會在12s以內)。
可以看到,通過多域名備份和快速超時的策略,即使用戶在網絡或者服務異常的情況下,也能快速恢復或者讓用戶能感知到自身網絡問題。
渲染優化
當我們在模板層和網絡層優化到極致的時候,限制我們的就是 WebView 的渲染速度了!
服務端預渲染
正常來講,正常的內容數據可能是類似 JSON 等數據,客戶端獲取到數據之后,將數據注入給前端,前端還需要將 JSON 數據跟模板進行組裝,拼上 HTML 標簽等模板了之后再呈現到 WebView 渲染,導致前端渲染上耗時也比較久。
為了提高用戶的首屏效率,我們在服務端就會把所有的詳情頁正文的 HTML 數據組裝好,通過將服務端直出內容注入到頁面中時,可以直接給 WebView 進行渲染,對于其他動態下發的內容(比如相關搜索),前端再進行二次異步處理,提升用戶效率。
客戶端渲染
一般來說,我們正文中所有內容都是通過 WebView 渲染,經過上述的優化之后,文章的文字部分渲染效率已經很高了,但是實際場景中,很多文章會包含比較多的圖片和視頻場景。
在實際場景中,WebView 渲染非文字內容會存在以下問題:
所以在詳情頁中,我們會將圖片和視頻等非文字內容通過原生組件的方式放在客戶端進行渲染,既可以提高渲染效率,也可以減少不必要的流量消耗。
原生化渲染還有一個好處,圖片越來越成為文章體驗的重要部分,對于多圖文章,我們在 Feed 頁面也可以智能加載詳情頁需要的圖片,增加用戶的文章首屏體驗。
白屏優化
講完了性能優化,最后再分享一下我們對詳情頁白屏率的一些優化,其實很多用戶反饋白屏問題大部分都可能是由于網絡等問題導致頁面加載時間過長,導致用戶從體驗上觀感是白屏了,這部分通過上面分享的性能優化手段已經能夠解決,所以下面只是簡單介紹下一些非網絡原因的白屏問題。
我們通過白屏檢測和上報之后的數據分析之后發現,非網絡原因導致的詳情頁的白屏問題大體是 WebView 加載的問題。
在 iOS 中,我們使用的是系統提供的 WKWebView,WKWebView 是運行在一個獨立進程中的組件,所以當 WKWebView 上占用內存過大時,WKWebView 所在的 WebContent Process 會被系統 kill 掉,反映在用戶體驗上就是發生了白屏。
根據網上的做法,我們可以在 ?WKWebView 提供的回調 webViewWebContentProcessDidTerminate 函數中通過 reload 方法重新加載當前頁面恢復,但是這種情況只適用于通過 loadRequest 加載的請求,在詳情頁中,由于使用了模板化的 WebView 中,重新 reload 只能重新 reload 模板,并不能正?;謴驼麄€詳情頁,需要客戶端重新加載模板之后再重新注入數據。
另外由于我們有預熱模板的邏輯,所以可能在進入詳情頁的時候使用的 WKWebView 就已經崩潰,在調用 JS 注入數據時會直接返回失敗,失敗時,我們會嘗試重新加載模板。但后來實際操作中發現一個問題,如果直接調用數據注入的方法,等待系統 WebView 返回失敗的回調耗時比較久,所以后續也調整了數據注入的接口,我們提前在注入的腳本中判斷是否存在數據注入的接口,如果不存在,就說明模板存在問題,直接重試即可。
而在 Android 中,我們采用的是自研內核 WebView,也會遇到一些奇奇怪怪的坑。
當然不管是 iOS 和 Android, WebView 加載的邏輯都比較復雜,有時候怎么重試也無法成功,這個時候我們會直接降級到加載線上的詳情頁,優先保證用戶的體驗。
總結
限于篇幅原因,我們還做了很多其他事情,包括請求精簡,push 文章預拉取,數據注入的方式優化等等,也做了很多其他的方向的探索,這里不做展開,希望有機會能再分享給大家。
最后總結一下我們在優化詳情頁打開速度之后的一些想法
- 數據很重要,我們在優化加載速度之前做的第一件事情其實是建立了一個詳情頁的數據看板,只有通過數據我們才能真正了解目前線上用戶的現狀,從真實用戶的體驗中找到瓶頸和優化點。
- 用戶體驗優先,優化方案有很多,除了加載速度之外,還需要從整體應用體驗出發,選擇對用戶最佳的方案
- 追求極致,其實最開始的優化是比較簡單的,但是越到后面越難,需要一點點摳細節,才能達到極致的用戶體驗
今日頭條技術團隊不僅致力于在業務上不斷深耕挖掘,在技術上也一直在追求極致的用戶體驗。
如果你也向往在一個億級DAU業務里成長,也期待在技術上有突飛猛進的提升,歡迎你加入我們。
無論你是 iOS/Android/前端/后端,我們在深圳/北京/廣州等你來,一起做更有挑戰的事!簡歷投遞郵箱:tech@bytedance.com ?;郵件標題:?姓名-工作年限-頭條技術團隊??。
總結
以上是生活随笔為你收集整理的白屏优化_今日头条品质优化 图文详情页秒开实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言注释部分两侧的分界符号分别是,C语
- 下一篇: c语言函数定义的语法格式,C语言函数-C