騰訊X5核心 WebView 實踐總結

彌宣發表於2019-01-24

本篇文章是基於 騰訊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監測的資源載入時序

chrome://inspect

Network 皮膚突出顯示兩種事件:DOMContentLoadedload

解析頁面的初始標記時會觸發 DOMContentLoaded。 此事件將在Network 皮膚上的兩個地方顯示:

  1. Overview 窗格中的藍色豎線表示事件。
  2. Summary 窗格中,您可以看到事件的確切時間。

頁面完全載入時將觸發 load。此事件顯示在三個地方:

  1. Overview 窗格中的紅色豎線表示事件。
  2. Requests Table 中的紅色豎線也表示事件。
  3. 在 Summary 窗格中,您可以看到事件的確切時間。

分析上圖:

  1. DOMContentLoadedload 事件觸發時機與Android端的回撥觸發時機不一致。
  2. 第一次onPageFinished()方法的呼叫和 document 型別檔案載入完成時間相近,且經過多次測試是在該檔案載入完成後呼叫。
  3. 第二次onPageFinished()方法回撥時間和load時間相近。

初步總結:

  1. 第一次onPageFinished()方法是在document型別檔案載入完成後呼叫的。
  2. 第二次onPageFinished()方法是在load完成時回撥。
  3. 通過仔細檢視shouldOverrideUrlLoadingonPageStarted方法時間差以及 圖中 Overview 欄,會發現載入網頁不是第一時間去請求資料的。所以 onPageStarted()方法較觸發是有一定的延遲時間。

ready 替換 onPageFinished 實現

據上分析的結果我們會發現,onPageFinished()方法會呼叫多次,所以,如果我們將業務邏輯放到該方法中執行,如果不做控制,勢必會出現一些問題。當然,由於網頁型別的多樣性,即使做了控制,依然會在特定的頁面出現問題。

那麼我們如何擺脫對onPageFinished()的依賴呢?

網頁的載入狀況,前端肯定會有生命週期的感知,那麼我們為什麼不依賴前端的通知來觸發Native邏輯呢?

通過上述的思考,Native的事件觸發完全交給前端去主動調取,而不是通過不靠譜的WebView回撥。在前端的$.ready()方法中去通知移動端開始執行業務邏輯。

並且這種方式在時序效能方面有很大提升,比第二次onPageFinished()觸發時機早很多(在較為複雜的頁面相差更大)

單頁應用

上面我們通過 ready() 的主動通知,實現了 onPageFinished() 方法中業務邏輯的優化。

但是,在單頁應用的網頁中,$.ready() 只在主頁面渲染完成時觸發一次,在子頁面並不會觸發,而且,WebViewshouldOverrideUrlLoading()onPageStarted() 方法都不會回撥。在一些單應用網頁會觸發 onPageFinished() 方法,它去請求了新的資源,所以我們感知到了回撥。而個別網頁並沒有去請求新的資源,直接對資源進行了替換,這種情況,我們就感知不到 onPageFinished() 的回撥。

當然,如果開發自己的頁面就不存在這些多情況的處理,可以協商解決方案。

本文的主要實現是基於第三方網頁做的功能擴充套件,所以需要考慮這些相容性問題。

給出不成熟的參考方案:

  1. 前端 url 變化監聽,通知移動端頁面變化。
  2. 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) {
      }
  });
複製程式碼

可以實現預載入及手動快取的功能。優化使用者體驗並且減少多次訪問造成的流量浪費。

除錯

打造最舒適的 WebView 除錯環境

相關文章