目前本人所負責的APP中使用了大量的h5頁面,所以在使用h5資源時,性能優化是作為客戶端開發的重點。雖然現在APP已經使用了 webView自帶緩存、硬件加速、等緩存方案,但是針對首次打開速度仍然很慢。因此項目組提出了h5離線方案。
h5 離線優點
1.節省流量:下載資源包為非一次性消耗資源,每次加載 h5 都可以從本地獲取。
2.秒開:不需要通過遠端加載 h5 資源。
3.防劫持:不需要從遠端加載 h5 資源。
實現方案
離線方案對於不同開發人員有不同的實現方案,本人提供的方案也不一定是最好的方案,所以參考即可。
1.將 h5 需要的資源下載到本地。
2.根據 h5加載鏈接路徑和本地資源路徑做一個映射
3.加載時攔截 h5 資源,根據 h5 遠程路徑獲取本地路徑,直接加載本地數據。
目前使用的映射方案,例如:
遠程鏈接:http://www.baidu.com?path1/path2/path3/index.html
對應的本地路徑:根路徑/www.baidu.com/path1/path2/path3/index.html
注:其中本地路徑中:www.baidu.com 和 path1、path2 等都為文件夾名稱。其中最后的 index.html 為具體文件。
ios 端實現方案
此處需要使用 URLProtocol 進行攔截。需要使用到 URLProtocol 的協議方法
func canInit(with request: URLRequest) -> Bool
func canonicalRequest(for request: URLRequest) -> URLRequest
func startLoading()
func stopLoading()
解決WKWebView 通信問題
在使用 URLProtocol 進行資源攔截的時候,如果使用 UIWebView 則沒有問題。但是如果使用WKWebView 則會有問題:URLProtocol 無法攔截到 WKWebView 上的 request 請求。此時,需要使用私有 api ,通過注冊 http(s) 則,可以使用 URLProtocol 攔截到 WKWebView 的 request。
類
browsingContextController
注冊 scheme
registerSchemeForCustomProtocol
取消注冊 scheme
unregisterSchemeForCustomProtocol
WKWebView 后遺症:body 丟失問題
WKWebView 那些坑
蘋果源碼
通過私有 api 雖然可以攔截到 http(s) 請求,但是對於 http(s) 的 post 請求則會丟失 body。原因為app 和 WKWebView 為兩個進程,通過注冊 scheme 可以將 WKWebView 中的 request 通過 IPC 通信傳遞到 app 進程中,所以才可以攔截到 request。但是在經過 IPC 通信時,會將 request 進行 encode,在 encode 的過程中,為了提高性能以及安全會將 body 和 bodyStream 進行丟失,從而造成 post 請求丟失 body 的情況。
為了解決 body 丟失問題,可以有以下方案
注:post 請求與是否攔截成功無關,是由於走 IPC 通信丟失的。
方案 1:使用 UIWebView
使用 UIWebView 不會造成跨進程,也就不需要跨進程通信,也就不需要注冊 scheme 等,所以可以完美解決,但是如果必須使用 WKWebView,則使用其他方案。
方案 2:自定義 scheme
因為注冊 http(s) 才能讓 http(s) 請求通過 IPC 通信,而造成 body 丟失,所以可以選擇使用自定義 scheme 的方案。這樣就會讓自定義的 scheme 通過 IPC 通信。
優點:
1、可以避開http(s) 經過 IPC 通信,從而避免丟失 body
缺點:
1、需要將h5 離線資源的 shceme 都換為自定義的 scheme
2、需要后端做大量工作
方案 3:WKURLSchemHandler
該方案和方案 2 一樣,都是需要自定義 scheme 並且 WKURLSchemHandler 需要 iOS11 以后才能使用
方案 4:使用 hook-ajax 的方案
hook-ajax方案原理為使用一段 js 代碼,將 xmlhttprequest 進行代理一份,這樣避免 body 丟失。
可行性:
1.該方案需要注入一段 js 代碼,網上有
2.作為客戶端開發,最好需要對 js 代碼有一定的了解
3.僅僅對 xmlhttprequest 有效,針對 fetch 請求無效。
注:hook-ajax 僅對 XMLHTTPRequest 有效,但是目前使用的 h5 資源中使用了大量的 fetch 請求所以暫時沒有使用。
方案 5:使用頻繁 register 和 unregister 的方案
因為當時 app 每次加載 url 都是使用一個新的 controller,所以可以在每次加載controller 時判斷是否為離線資源鏈接,如果為離線資源鏈接,則 register scheme,否則 unregister scheme。
注:考慮到 h5 資源內部有一些上報埋點等 post 請求。如果此時 register scheme 則會造成埋點 post 請求丟失。
方案 6:使用本地服務器
該方案持續研究中。。。
總結
h5 離線開發可以提高加載 h5 速度,防止服務器劫持等優點,但是攔截 h5 資源需要使用 URLprotocol,但是 攔截 WKWebView 會出現 post 請求丟失 body 問題,所以使用 wkwebview 需要首先解決 post 請求 body 丟失問題。丟失原因為:
1.攔截 WKWebView 的 h5 資源需要執行私有 api,進行 register scheme 和 unregister scheme.
2.執行私有 api 會走 IPC 通信,造成 post 請求丟失 body 的情況。
注:鑒於post 請求丟失 body 問題和目前現有的解決方案不能 100% 避免帶來的問題,或解決了改問題可能帶來其他的問題,所以iOS 端暫未實行 h5 離線方案