愛伊米

WKWebview 秒開的實踐及踩坑之路

關注後回覆 “進群” ,拉你進群交流

最佳化背景

眾所周知,H5 的部分優勢(開發快,迭代快,熱更新)是很明顯的,公司客戶端的部分業務都是由 H5 來實現的,網路好的情況下體驗也是很不錯的

但是其實 H5 的體驗是比原生差的,這就需要想辦法如何提高 H5 載入速度,最佳化體驗,首屏的載入速度還是很影響體驗的

載入速度

關於載入速度慢有很多文章都已經詳細解釋了,h5在載入工作中做了很多事

初始化  -> 請求頁面 -> 下載資料 -> 解析HTML -> 請求  資源 ->  渲染 -> 解析  執行 ->  請求資料 -> 解析渲染 -> 下載渲染圖片

一般頁面在  渲染後才能展示,可以發現, 首屏渲染白屏問題的原因關鍵在於,如何最佳化減少從請求下載頁面到渲染之間這段時間的耗時。

前後端最佳化

這其中可做的最佳化特別多,前後端能夠做的是

降低請求量:減少  請求數, 合併資源, 壓縮,,。

因為手機瀏覽器同時響應請求是 4 個,4 個的請求數也許不是特別靠譜,沒有查到出處,但是肯定是越少越好

協議快取請求,離線快取 ,離線資料快取 。

加快請求速度:預解析 ,減少域名數,並行載入, 分發。

渲染: 最佳化,載入順序,服務端渲染模板直出。

一般情況下,只要對照這個列表,對比差異就基本能搞定絕大部分前端效能問題了。不過我們在裡面仔細再分析下,對首屏啟動速度影響最大的就是網路請求,包括請求 、、 等靜態資源和展示資料的請求。所以客戶端內,最佳化最關鍵的其實就是如何快取這些網路資源,也就是離線包快取方案。

離線包方案的實踐

方案選型是兩種

基於  實現  離線資源載入

使用  實現  離線資源載入

LocalWebServer

基於 iOS 的 ,目前大致有以下幾種較為完善的框架:

(支援 、 及多種網路場景)

(基於 ,不支援  及 )

( 實現,功能較上面兩類更完善)

當時採用的是 ,在開啟 APP 後直接啟動,H5 的連結直接替換成本地  + 埠號連結的地址。

本來的方案是本地伺服器和遠端h5伺服器同步下載資源,下載後客戶端請求本地伺服器的路徑,如未找到相應的資源再請求遠端伺服器的資原始檔。

測試過程中碰到很多奇怪的問題(暫不一一舉例),也有提到以下問題並且時間緊急所以並未做進一步的深入:

資源訪問許可權安全問題

APP 前後臺切換時,服務重啟效能耗時問題

服務執行時,電量及  佔有率問題

多執行緒及磁碟  問題

WKURLSchemeHandler

關於離線包

前端專案的靜態資源直接打包成  包,APP 在啟動時開始下載該包並解壓到本地。 透過  攔截並載入本地資原始檔。關於離線包的分發,就是普通的  離線包和一個版本控制的  檔案,每次打離線包會修改  檔案裡的版本號,並附有離線包下載地址。此處可以最佳化的更好,但暫時並不需要太複雜。

離線包的下載和解壓

只是簡單的下載並解壓到本地資源路徑,關於版本比對的程式碼這裡沒有展示出來,自行注意,避免每次都全量更新。

WKWebview 快取池

美團有篇文章提到,在使用  的模擬器測試  的載入速度,首次初始化的時間耗時有  多毫秒。其實本人用  的真機,發現初始化的時間約在  毫秒左右甚至更短。雖然只佔整個載入時間的特別小的一部分,但是本著能優則優的原則還是做了處理,也就是預載入 。

新建了一個單例類 ,預設快取池裡的數量是 10 個

在合適的地方提前呼叫 ,自行選擇在  或主頁面初始化的時候呼叫。

建立  的方法如下,需要注意的是 , 需要註冊這個  才能實現攔截,這個是  攔截需要的準備工作。

是我的自定義攔截類

關於這裡的版本為什麼設定成  以上, 是蘋果  就已推出,但是有發現某款機型在  上攔截失效,導致產生  白屏。所以這裡一刀切,直接 12 以上才處理。其實  一下的使用者量特別少,所以不需要太擔心。

替換 url scheme

這裡是透過規則直接把  替換為 ,也就是替換  為自定義協議,完成這一步後,攔截生效。

需要注意的有兩點:

前端這邊載入 js 等資源都是用相對路徑,前端的 ajax 請求,像 post 請求,scheme 使用 http(s) 不使用自定義協議,這樣native 不會攔截,完全交給 H5 與伺服器互動,就不會發生髮送 post 請求,body 丟失的情況。

在我的專案裡,H5 對伺服器的請求都是透過 native 端來轉發的,所以也不存在攔截 post 請求,body 丟失的情況。所以上面這樣的改動對 H5 端是無侵入式的,不需要修改業務程式碼。

最最重要的自定義 SDICustomURLSchemeHandler 類

上面的程式碼是攔截資源請求後的處理程式碼。收到攔截請求後,先獲取本地資源包對應的資源,轉換成  回傳給  進行渲染處理;若本地沒有,則  替換成  的  重發請求通知 ,這就是基本流程。

以下就是載入本地資源和重發請求的程式碼

整個過程中遇到的一些踩坑點

1. 'The task has already been stopped'崩潰問題

是一個 ,它裡面存的是以當前的 做 key,攔截開始時設定 YES,收到停止通知時設定 NO。這是由於在快速切換  時,之前的  已經停止但是後面再次呼叫了它的方法就會產生該崩潰。

在實際使用過程中,用  監控到還是會有該崩潰發生,只不過次數特別少,一天約四五條左右。還在尋找問題的原因中。

2. WKWebview 的預設快取策略問題

之前未考慮到  的預設快取策略( 預設快取策略完全遵循  快取協議)。

在 h5 打包上線並更新離線包後,H5 的資原始檔修改是變更  檔名的。由於快取策略預設時間是一個小時,會導致快取的  載入不到修改後的 js,css 等檔案(無論是本地離線包和遠端伺服器都已經沒有這個 md5 檔案)。

簡單的解決方案是透過資源連結加版本號字尾,每次更新資源的時候變更版本號,在上面的程式碼中有做這部分處理。既保證了實時的更新,又保證了載入速度。

3. uni-app 圖片 CDN 問題

做完上述的離線包最佳化後,發現新下載 APP 的情況,會偶發載入很慢問題。iOS 出現,但是 android 並未出現。

H5 部分是用  開發的,所以發現這個問題後由前端同事修復後恢復正常。

4. chunk-vendors.js 檔案過大

這個問題也是抓包發現的,在未開啟離線包快取開關時,發現h5的載入速度過慢,發現載入的  檔案過大約 。  方法裡會報  錯誤資訊  的錯誤資訊。也由前端同事處理了這個問題。

最終效果

統計了 APP 在不開離線包方案時, 平均載入時長在  秒的範圍內(這裡是計算的 開始載入到導航完成的時間),在上述最佳化完成後,開啟的時長在  秒之間。

所以效果還是很顯著的,使用者的直觀感受就是接近於秒開的體驗。

總結

上面的最佳化過程中踩了很多坑,但是也重新梳理了  的載入過程,預設快取策略機制等內容。上面的方案肯定不是最優的,只是一個快速達到  接近秒開效果的一個方案。

有什麼更好的解決方案或者上述文中有不對的地方,希望大家指出,歡迎共同討論~

-End-