Categories
程式開發

今日頭條品質優化:圖文詳情頁秒開實踐


背景

作為一個內容類應用,看新聞讀資訊一直是頭條用戶的核心需求,頁面的打開速度直接關係到用戶使用頭條的核心體驗,在頭條中,為了更多的承載足夠豐富的樣式和邏輯下保持多端體驗的統一,詳情頁的內容我們是通過WebView 來承載的,​​但WebView 本身的性能相比Native 來說比較差,因此,今日頭條技術團隊一直致力於優化詳情頁的加載速度。

經過不斷的優化,目前今日頭條中詳情頁在線上的打開體驗,從肉眼上基本已經感知不到加載過程。 在接下來這篇文章裡,我們會逐步拆解和介紹我們對詳情頁加載優化的思路和實踐。

先讓我們來看看優化前後的效果吧~

今日頭條品質優化:圖文詳情頁秒開實踐 1

今日頭條詳情頁加載體驗優化前

今日頭條品質優化:圖文詳情頁秒開實踐 2

今日頭條詳情頁加載體驗優化後

數據建立

性能

當我們開始著手優化頁面加載速度之前,我們需要明確一個問題,怎樣才是用戶真正體驗到的頁面加載時間。

首先我們可以看下面這個公式:

页面加载时间 = 页面加载完成时间 - 页面开始加载时间

頁面開始加載時間很好確定,當用戶點擊了Feed 上的卡片,我們就可以認為頁面開始加載了。

問題是怎麼定義頁面加載完成了呢? 從客戶端的角度上看,無論是iOS 還是Android,WebView 都提供了一個loadFinsih 的回調,但在實際應用中我們發現,loadFinish 回調並不能反應用戶的真實體驗。

一般來說,WebView 渲染需要經過下面幾個步驟

  1. 解析HTML 文件
  2. 加載JavaScript 和CSS 文件
  3. 解析並執行JavaScript
  4. 構建DOM 結構
  5. 加載圖片等資源
  6. 頁面加載完畢

而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.3秒 內完成頁面加載。

今日頭條品質優化:圖文詳情頁秒開實踐 3

80分位優化數據對比

再後來我們又發現,在頭條詳情頁的量級下面,即使是80 分位的數據也不能反應許多長尾用戶的真實情況,也為了更極致的追求詳情頁的加載性能,我們最後將詳情頁的性能口徑調整到95 分位。 到目前在我們的努力下,今日頭條詳情頁的加載速度95 分位也優化了將近80% 。

我們究竟做了什麼呢,接下來會慢慢介紹一下。

模板優化

模板拆分

如前所述,圖文詳情頁是通過WebView 來承載的,​​而WebView 承載頁面最簡單的做法就是直接通過URL 去加載一個線上頁面。 那麼先來一道簡單的面試題,當用戶從瀏覽器輸入一個URL 到頁面展現發生了什麼呢?

之前已經介紹過頁面的渲染流程了,現在我們再簡單看看用戶從點擊到看到頁面內容需要經歷如下幾個階段:

今日頭條品質優化:圖文詳情頁秒開實踐 4

WebView 加載流程

可以看到,通過線上頁面加載用戶每次進入詳情頁都要通過多次網絡加載,極容易受網絡波動的影響,這種情況下,也無法保證頁面加載的時長和成功率,極大的影響了用戶體驗。

於是在頭條中,我們將新聞中標題和正文內容進行拆分,把頭條詳情頁的公共樣式CSS 和邏輯JS 都抽離出來,形成一個獨立而完備的詳情頁模板,這樣我們就可以把模板直接內置在客戶端中。

同時我們會與前端約定好的JS 腳本,通過接口將正文內容數據注入頁面完成詳情頁的頁面展示,通過該這種方式我們可以將接口放到客戶端上進行請求。

這樣用戶進入詳情頁的時候只需要本地加載模板,而且加載模板的時候也可以同時並行請求詳情頁數據,再將數據注入進模板中。

那麼用戶點擊到看到頁面內容只需要經歷下面的階段:

今日頭條品質優化:圖文詳情頁秒開實踐 5

模板拆分

如上圖所示,我們只需要通過一次網絡加載就可以完成頁面渲染。

今日頭條品質優化:圖文詳情頁秒開實踐 6

還能不能更快一點呢? 當然能!

為了提高頁面的加載速度,客戶端通過一定的策略去預加載新聞數據,這樣在理想狀態下用戶進入頁面時看到頁面時就可以直接使用緩存的數據,用戶在看新聞的時候可以實現完全離線化,避免受到網絡的影響。

今日頭條品質優化:圖文詳情頁秒開實踐 7

本地加載

模板預熱

完全脫離了網絡加載之後,還能再快一點呢? 當然還是可以的!

當全流程離線化之後,頁面加載的瓶頸就變成了本地模板的加載時間,所以我們接下來要做的就是優化模板加載時間。

對於模板來說,我們做了兩件事情

  1. 模板合併,正常來說,WebView 需要在加載完主HTML 之後再去加載HTML 中的JS 和CSS,需要多次IO 操作,於是我們將JS 和CSS 還有一些圖片都內聯到一個文件中,這樣,加載模板時就只需要一次IO 操作,也大大減少因為IO 加載衝突導致模板加載失敗問題
  2. 模板簡化,我們將部分非必須的腳本異步化拉取,精簡不必要的樣式和JS 代碼,將模板大小壓縮了20% 以上

通過上面優化,我們就已經將模板加載時間大大優化了,但是還能不能更給力呢? 還是可以的。

對於客戶端來說,當模板跟數據分離之後,由於每次用戶點擊的時候加載的都是同一個模板,所以實際上,我們並不需要在用戶進入頁面的時候才去創建WebView 以及加載模板,我們只需要在合適的時機在後台創建WebView,並且提前預熱加載模板,當用戶點擊進入頁面的時候就能使用已經加載好模板的WebView,直接將詳情頁的內容數據通過JS 注入到頁面中,前端收到數據後進行頁面渲染即可。

此時用戶進入詳情頁實際就不再需要重新加載模板了,路徑就變成了:

今日頭條品質優化:圖文詳情頁秒開實踐 8

模板預熱

可以看下,通過本地測試的模板預熱和數據預取的優化效果,還是比較明顯的,基本上已經達到了上面的截圖中的驗證效果。

今日頭條品質優化:圖文詳情頁秒開實踐 9

本地測試數據

今日頭條品質優化:圖文詳情頁秒開實踐 6

模板復用

當我們拆分完模板和數據之後,數據上優化已經比較明顯,但我們說過,除了驗證數據,我們還需要看線上用戶的真實體驗數據,從95 分位上看實際數據優化卻不是很明顯,所以我們從數據上觀察,用戶預熱模板的命中率只有53%,還有進一步的提升空間。

今日頭條品質優化:圖文詳情頁秒開實踐 11

模板預熱率

為了盡可能的提高頁面的加載速度,我們希望用戶每次進入詳情頁的時候都能夠使用預熱好模板的WebView,一般情況下,我們都會使用模板預創建池的手段來優化用戶進入詳情頁時的預熱模板命中率。

但其實在很多情況下,WebView 的創建是一個性能開銷比較大的操作,如果我們使用預創建池的方案,那麼就會在後台頻繁創建WebView,這樣對用戶在Feed 場景的瀏覽體驗也會有一定的影響。

而且假如用戶頻繁且快速進出詳情頁時,實際場景中用戶也很容易遇到無法命中預熱模板的場景。

這個時候為了優化用戶的體驗,如前文所述,我們每次使用的時候都是同一個模板,所以我們使用完當前WebView 之後,只需要在用戶退出頁面的時候把正文數據清空,這樣進入下一個頁面的時候就能夠繼續復用這個WebView 重新註入數據即可。

今日頭條品質優化:圖文詳情頁秒開實踐 12

通過這個手段,我們既避免了頻繁在後台預創建WebView 對用戶刷Feed 體驗的影響,把用戶進入頁面時候的預熱模板命中率從53% 提升到92%,優化了用戶體驗。

今日頭條品質優化:圖文詳情頁秒開實踐 13

預熱模板命中率

網絡優化

說完我們在模板WebView 方面的優化之後,再介紹一下我們在內容請求上的優化。

CDN 加速

由於頭條詳情頁請求有以下特點

  1. 流量大,之前說過,看新聞作為用戶在頭條的核心場景,每天都有上億用戶在使用頭條,詳情頁的數據流量十分大。
  2. 數據屬性基本不變,在詳情頁的請求中,很多熱點文章是重複渲染計算的,正文、標題、作者信息、圖片控制以及一些樣式和業務邏輯渲染是基本不變的,這部分重複計算耗費了帶寬、服務器資源,是比較沒有必要的。
  3. 用戶分佈廣,網絡狀況難以保證,頭條的用戶量很大,覆蓋了各種運營商網絡和網絡狀態,網絡質量無法得到很好的保證。 而CDN 能夠將數據緩存在各地的邊緣節點,用戶就近接入了邊緣節點,避免在網絡質量無法保證的公網上長時間傳輸,從而提高了響應速度和響應的成功率。
  4. 接口數據大,由於正文數據的存在,接口返回的數據常常會很大,如果每一次都實時返回,對網絡的壓力會比較大,可能會把帶寬打滿而影響其他服務

所以我們將詳情頁內容數據分為靜態和動態兩部分,將正文內容、標題、作者欄等用戶主要消費的又基本不變的內容託管到了CDN 上。

CDN 的全稱是Content Delivery Network,即內容分發網絡。 其目的是通過在現有的Internet中增加一層新的網絡架構,將網站的內容髮佈到最接近用戶的網絡“邊緣”,使用戶可以就近取得所需的內容,提高用戶訪問網站的響應速度。 CDN 有別於鏡像,因為它比鏡像更智能,或者可以做這樣一個比喻:CDN = 更智能的鏡像+ 緩存+ 流量導流。 因而,CDN 可以明顯提高Internet網絡中信息流動的效率。 從技術上全面解決由於網絡帶寬小、用戶訪問量大、網點分佈不均等問題,提高用戶訪問網站的響應速度。

今日頭條品質優化:圖文詳情頁秒開實踐 14

託管到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

今日頭條品質優化:圖文詳情頁秒開實踐 15

  • 用戶突然進入了一個網絡很差的環境(假設上次成功請求的耗時為a)下一次請求:第一個CDN很快超時(a * 1.5);開始請求第二個CDN(a * 1.5)也超時;開始請求第三個CDN(a * 1.5)。 最後一個請求會在a * 3 + w 後返回失敗(這個值會在12s以內)。

今日頭條品質優化:圖文詳情頁秒開實踐 16

可以看到,通過多域名備份和快速超時的策略,即使用戶在網絡或者服務異常的情況下,也能快速恢復或者讓用戶能感知到自身網絡問題。

渲染優化

當我們在模板層和網絡層優化到極致的時候,限制我們的就是WebView 的渲染速度了!

服務端預渲染

正常來講,正常的內容數據可能是類似JSON 等數據,客戶端獲取到數據之後,將數據注入給前端,前端還需要將JSON 數據跟模板進行組裝,拼上HTML 標籤等模板了之後再呈現到WebView 渲染,導致前端渲染上耗時也比較久。

為了提高用戶的首屏效率,我們在服務端就會把所有的詳情頁正文的HTML 數據組裝好,通過將服務端直出內容注入到頁面中時,可以直接給WebView 進行渲染,對於其他動態下發的內容(比如相關搜索),前端再進行二次異步處理,提升用戶效率。

客戶端渲染

一般來說,我們正文中所有內容都是通過WebView 渲染,經過上述的優化之後,文章的文字部分渲染效率已經很高了,但是實際場景中,很多文章會包含比較多的圖片和視頻場景。

在實際場景中,WebView 渲染非文字內容會存在以下問題:

  1. 相比於文字內容,非文字內容比如說圖片和視頻類資源的渲染對於WebView 來說渲染效率比較差
  2. 在詳情頁中文章有大量圖片的場景,對於WebView 的渲染內存佔用和滑動體驗也有問題
  3. 最後,如果用戶多次打開同一篇文章,這篇文章中的圖片也會存在多次加載的問題,無法與客戶端進行緩存共享,對用戶的流量也是一種浪費。

所以在詳情頁中,我們會將圖片和視頻等非文字內容通過原生組件的方式放在客戶端進行渲染,既可以提高渲染效率,也可以減少不必要的流量消耗。

原生化渲染還有一個好處,圖片越來越成為文章體驗的重要部分,對於多圖文章,我們在Feed 頁面也可以智能加載詳情頁需要的圖片,增加用戶的文章首屏體驗。

白屏優化

講完了性能優化,最後再分享一下我們對詳情頁白屏率的一些優化,其實很多用戶反饋白屏問題大部分都可能是由於網絡等問題導致頁面加載時間過長,導致用戶從體驗上觀感是白屏了,這部分通過上面分享的性能優化手段已經能夠解決,所以下面只是簡單介紹下一些非網絡原因的白屏問題。

我們通過白屏檢測和上報之後的數據分析之後發現,非網絡原因導致的詳情頁的白屏問題大體是WebView 加載的問題。

在iOS 中,我們使用的是系統提供的WKWebView,WKWebView 是運行在一個獨立進程中的組件,所以當WKWebView 上佔用內存過大時,WKWebView 所在的WebContent Process 會被系統kill 掉,反映在用戶體驗上就是發生了白屏。

根據網上的做法,我們可以在WKWebView 提供的回調 webViewWebContentProcessDidTerminate 函數中通過reload 方法重新加載當前頁面恢復,但是這種情況只適用於通過loadRequest 加載的請求,在詳情頁中,由於使用了模板化的WebView 中,重新reload 只能重新reload 模板,並不能正常恢復整個詳情頁,需要客戶端重新加載模板之後再重新註入數據。

另外由於我們有預熱模板的邏輯,所以可能在進入詳情頁的時候使用的WKWebView 就已經崩潰,在調用JS 注入數據時會直接返回失敗,失敗時,我們會嘗試重新加載模板。 但後來實際操作中發現一個問題,如果直接調用數據注入的方法,等待系統WebView 返回失敗的回調耗時比較久,所以後續也調整了數據注入的接口,我們提前在註入的腳本中判斷是否存在數據注入的接口,如果不存在,就說明模板存在問題,直接重試即可。

而在Android 中,我們採用的是自研內核WebView,也會遇到一些奇奇怪怪的坑。

  1. 多線程讀模板文件問題,WebView 在運行中會讀取的文件模板,如果此時另外一個線程同時更新模板文件時,就出現了模板加載問題,所以需要保證模板加載的原子性
  2. Render 卡死問題,內核是一個比較複雜的邏輯,內部渲染極少數情況也會出現Render 卡死問題,但是在詳情頁整體用戶的量級下,即使只有十萬分之一的可能,對用戶來說也是一個比較大的問題,此時我們會從業務上做白屏監控進行重試

當然不管是iOS 和Android, WebView 加載的邏輯都比較複雜,有時候怎麼重試也無法成功,這個時候我們會直接降級到加載線上的詳情頁,優先保證用戶的體驗。

總結

限於篇幅原因,我們還做了很多其他事情,包括請求精簡,push 文章預拉取,數據注入的方式優化等等,也做了很多其他的方向的探索,這裡不做展開,希望有機會能再分享給大家。

最後總結一下我們在優化詳情頁打開速度之後的一些想法

  • 數據很重要 ,我們在優化加載速度之前做的第一件事情其實是建立了一個詳情頁的數據看板,只有通過數據我們才能真正了解目前線上用戶的現狀,從真實用戶的體驗中找到瓶頸和優化點。
  • 用戶體驗優先 ,優化方案有很多,除了加載速度之外,還需要從整體應用體驗出發,選擇對用戶最佳的方案
  • 追求極致 ,其實最開始的優化是比較簡單的,但是越到後面越難,需要一點點摳細節,才能達到極致的用戶體驗

本文轉載自公眾號字節跳動技術團隊(ID:)。

原文鏈接

今日頭條品質優化:圖文詳情頁秒開實踐