本篇文章是基於 騰訊X5核心 WebView 實踐的總結篇,較上篇文章更為完整,具體。
onPageFinished() 回撥時機
通過 WebView
的回撥函式,分析 onPageFinished()
回撥時機
載入某個網址的Android端回撥監測如下:
shouldOverrideUrlLoading time: 1519274808392
onPageStarted: time: 1519274808561 // 169ms
onPageFinished time: 1519274809735 // 1174ms
onReadableCallback: false
shouldOverrideUrlLoading time: 1519274811067 --url :shanbay.native.app://document/ready
onReadableCallback: true
onPageFinished time: 1519274817879 // 9318ms
onReadableCallback: true
複製程式碼
據上資料分析:
第一次onPageFinished()
回撥觸發是在 1174ms (較onPageStarted()
方法)
第二次onPageFinished()
回撥觸發是在 9318ms
通過 chrome://inspect
監測的資源載入時序
Network 皮膚突出顯示兩種事件:DOMContentLoaded
和 load
解析頁面的初始標記時會觸發 DOMContentLoaded
。 此事件將在Network 皮膚上的兩個地方顯示:
- Overview 窗格中的藍色豎線表示事件。
- 在 Summary 窗格中,您可以看到事件的確切時間。
頁面完全載入時將觸發 load。此事件顯示在三個地方:
- Overview 窗格中的紅色豎線表示事件。
- Requests Table 中的紅色豎線也表示事件。
- 在 Summary 窗格中,您可以看到事件的確切時間。
分析上圖:
DOMContentLoaded
和load
事件觸發時機與Android
端的回撥觸發時機不一致。- 第一次
onPageFinished()
方法的呼叫和document
型別檔案載入完成時間相近,且經過多次測試是在該檔案載入完成後呼叫。 - 第二次
onPageFinished()
方法回撥時間和load
時間相近。
初步總結:
- 第一次
onPageFinished()
方法是在document
型別檔案載入完成後呼叫的。 - 第二次
onPageFinished()
方法是在load
完成時回撥。 - 通過仔細檢視
shouldOverrideUrlLoading
和onPageStarted
方法時間差以及 圖中 Overview 欄,會發現載入網頁不是第一時間去請求資料的。所以onPageStarted()
方法較觸發是有一定的延遲時間。
ready 替換 onPageFinished 實現
據上分析的結果我們會發現,onPageFinished()
方法會呼叫多次,所以,如果我們將業務邏輯放到該方法中執行,如果不做控制,勢必會出現一些問題。當然,由於網頁型別的多樣性,即使做了控制,依然會在特定的頁面出現問題。
那麼我們如何擺脫對onPageFinished()
的依賴呢?
網頁的載入狀況,前端肯定會有生命週期的感知,那麼我們為什麼不依賴前端的通知來觸發Native
邏輯呢?
通過上述的思考,Native
的事件觸發完全交給前端去主動調取,而不是通過不靠譜的WebView
回撥。在前端的$.ready()
方法中去通知移動端開始執行業務邏輯。
並且這種方式在時序效能方面有很大提升,比第二次onPageFinished()
觸發時機早很多(在較為複雜的頁面相差更大)
單頁應用
上面我們通過 ready()
的主動通知,實現了 onPageFinished()
方法中業務邏輯的優化。
但是,在單頁應用的網頁中,$.ready()
只在主頁面渲染完成時觸發一次,在子頁面並不會觸發,而且,WebView
的 shouldOverrideUrlLoading()
及 onPageStarted()
方法都不會回撥。在一些單應用網頁會觸發 onPageFinished()
方法,它去請求了新的資源,所以我們感知到了回撥。而個別網頁並沒有去請求新的資源,直接對資源進行了替換,這種情況,我們就感知不到 onPageFinished()
的回撥。
當然,如果開發自己的頁面就不存在這些多情況的處理,可以協商解決方案。
本文的主要實現是基於第三方網頁做的功能擴充套件,所以需要考慮這些相容性問題。
給出不成熟的參考方案:
- 前端
url
變化監聽,通知移動端頁面變化。 - 在
onPageFinished()
方法中再去做一個保底操作,損失一部分效能換取使用者的響應速度。
Js 注入時機以及時序控制
網路上的普遍做法是在 onPageFinished()
中注入 Js
指令碼。
這種做法存在一些問題:
-
可能會注入多次。
-
onPageFinished()第二次呼叫時機很遲,在複雜的頁面效能損失很大。
-
如果注入太多,會影響頁面的體驗。
由於專案注入的指令碼行數達到 1W+,所以我們需要對時序做一些優化。保證呼叫時我們已經完成了注入。
這裡我們主要注入生成一個 script
標籤。
webView.loadUrl("javascript:(function() {" +
"var scriptElement = document.getElementById('readability-script');" +
"var parent = document.getElementsByTagName('body').item(0);" +
"if(parent && !scriptElement) {" +
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
"script.id = 'readability-script';" +
// Tell the browser to BASE64-decode the string into your script !!!
"script.innerHTML = window.atob('" + mAssetsScript + "');" +
"parent.appendChild(script);}" +
"})()");
複製程式碼
通過控制標籤的唯一性來防止注入多次; 在頁面初始化前完成本地 js 指令碼的檔案讀取;不斷嘗試注入直到可以注入為止。
在 onProgressChanged()
回撥中,不斷的嘗試讀取節點注入指令碼。
通過最開始對 onPageFinished()
的分析。是否可以嘗試在第一次回撥時開始注入指令碼。但是,不能保證每個網頁都會回撥兩次onPageFinished()
。
通常情況下,CSS不會阻塞HTML的解析,但如果CSS後面有JS,則會阻塞JS的執行直到CSS載入完成(即便JS是內聯的指令碼),從而間接阻塞HTML的解析。
資源載入回撥
在研究WebView
載入時序時發現了這個資源載入的回撥onLoadResource()
。這裡簡單介紹下,針對這個回撥,可以做的事情很多。
在載入頁面資源時會呼叫,每一個資源(比如圖片)的載入都會呼叫一次。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean onLoadResource(WebView view, String url) {
}
});
複製程式碼
可以實現預載入及手動快取的功能。優化使用者體驗並且減少多次訪問造成的流量浪費。