高階前端進階系列 - webview

麥麥子發表於2019-05-10

從jsBridge功能說起

H5頁面被webview承載,需要實現 js 和 native 的通訊,常用的功能如圖:

高階前端進階系列 -  webview

例如:


// 開啟新容器承載H5
this.state.jsBridge.openWebView({   
 'action': 'https://XXX.XXX.com',   
 'login': true
})複製程式碼

// 銷燬webview
this.state.jsBridge.quitAction()複製程式碼

// 控制客戶端頂部back按鈕-觸發回撥函式
this.state.jsBridge.handleBackAction("openCancelPayModal", window.openCancelPayModal, true);複製程式碼

所以整個通訊流程就像是這樣

高階前端進階系列 -  webview

客戶端會將jsBridge物件注入到window物件下,當呼叫jsBridge觸發(例如獲取登入態)事件方法都會被客戶端特有的攔截器所攔截並處理。

jsBrige通訊模型優化

當js觸發jsBridge物件觸發某一個方法,其實都會走到客戶端的攔截器裡面然後native響應,這個過程就是在向客戶端發訊息。而客戶端回撥前端callback函式並且將資料返回其實就是在向H5頁面發訊息。那基於這種訊息傳送機制,可以將通訊模型進一步優化。

高階前端進階系列 -  webview


優點:

1.前端和客戶端不需要過多維護jsBridge方法,只需要維護功能短鏈,例如:

// 開啟新容器,並且要求使用者已登入
this.state.jsBridge.emit({
    name:'page1Event',
    action: 'XXX/openWebView',
    globle: false,
    params: {
        login: true
    }
})複製程式碼

其中action就是指的功能短鏈,而且emit方法就是通用發起訊息的bridge。

而用於接收返回訊息的函式,在老通訊模型中是需要通過callback的形式傳遞。新的通訊模型不再需要繁瑣的callback傳遞,而是使用通用的function 來接收,例如:

//通用的頂層方法接收客戶端訊息
window.onJsBridgeEvent = function (json) { console.log(json) }複製程式碼

ps:globle是用來處理全域性訊息,要避免效能浪費要預設為不開啟。開啟globle需要針對頁面name名稱進行處理,需要頁面事件名稱保證唯一性(例如:A頁面配置在tab,在其他tab內還能夠再次開啟A頁面,此時A頁面有兩個,如果頁面事件名稱一致會導致全域性訊息被兩個A頁面處理,但是我們這時候只想要訊息被置於容器頂層的A頁面單獨接受)


2.滿足更多的互動場景

高階前端進階系列 -  webview

webview3中傳送訊息通知客戶端銷燬webview2,那麼基於訊息傳送的通訊模型就能夠實現。
當前開啟的所有webview都能接收客戶端全域性訊息,例如:退登,集體銷燬等功能的實現。


通訊原理

剛才有提到jsBridge注入,客戶端攔截器。

android

android 呼叫 js 程式碼的 方法:

1.通過webview的loadurl
2.通過webview的evaluateJavascript

jsBridge注入和攔截器

1.通過WebView的addJavascriptInterface()進行物件對映
2.通過 WebViewClient 的shouldOverrideUrlLoading ()方法回撥攔截 url
3.通過 WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回撥攔截JS對話方塊alert()、confirm()、prompt()訊息

ios

使用WebViewJavaScriptBridge

function setupWebViewJavascriptBridge(callback) {
	if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
	if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
	window.WVJBCallbacks = [callback];
	var WVJBIframe = document.createElement('iframe');
	WVJBIframe.style.display = 'none';
	WVJBIframe.src = 'https://__bridge_loaded__';
	document.documentElement.appendChild(WVJBIframe);
	setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}複製程式碼

為了搭配理解這段程式碼的作用,可以點選這裡

WebViewJavascriptBridge是ios提供的bridge物件,但是在最開始容器層初始化的時候是不提供的,所以程式碼會繼續向下進行,直到 WVJBCallbacks(儲存需要回撥的函式) 和 wvjbscheme://__BRIDGE_LOADED__執行。

 wvjbscheme://__BRIDGE_LOADED__這個協議的目的就是用來創WebViewJavascriptBridge,相關截圖如下:

127行:github.com/marcuswesti…

高階前端進階系列 -  webview

到這裡jsbridge物件就算是提供給了前端使用。

接下來是呼叫ios能力,例如呼叫客戶端實名認證:

this.state.jsBridge.callHandler("realNameAuthentication");複製程式碼

callHandler為jsbridge裡面的一個物件,callHandler下的_doSend方法用來通知給客戶端進行處理,相關截圖如下:

高階前端進階系列 -  webview

高階前端進階系列 -  webview

高階前端進階系列 -  webview

這裡的 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;為:

messagingIframe.src = https + '://' + "__wvjb_queue_message__"; 這裡的 __wvjb_queue_message__作用為觸發ios的攔截器來進行處理。

本質上,最終還是被攔截器所攔截,但是ios的UI webview和WK webview攔截器會不同:

UI   webview : shouldStartLoadWithRequest
WK webview :decidePolicyForNavigationAction


webview效能優化

webview渲染過程

高階前端進階系列 -  webview

( 摘自:美團技術團隊 tech.meituan.com/2017/06/09/… )


從使用者使用的過程,大致如下:

1.開啟了一個新的視窗

2.頁面白屏

3.頁面基本骨架渲染出來,但是沒有資料

4.資料獲取完成,頁面整體渲染結束

慢的一部分原因:webview去載入url並不像是 瀏覽器 載入url的過程,webview存在一個初始化的過程。

webview init

高階前端進階系列 -  webview

為了提升init時間,常用做法是:

app啟動時初始化一個隱藏的webview等待使用,當使用者點選需要載入URL,直接使用這個webview 來載入,從而減少webview init 初始化時間。弊端就是帶來了額外的記憶體開銷。


webview快取使用

為了提升H5載入速度,會使用到webview快取模式。

以安卓為例:

LOAD_CACHE_ONLY: 不使用網路,只讀取本地快取資料,
LOAD_DEFAULT:根據cache-control決定是否從網路上取資料,
LOAD_CACHE_NORMAL:API level 17中已經廢棄, 從API level 11開始作用同- - LOAD_DEFAULT模式,
LOAD_NO_CACHE: 不使用快取,只從網路獲取資料,
LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用快取中的資料。
如果一個頁面的cache-control為no-cache,在模式LOAD_DEFAULT下,無論如何都會從網路上取資料,如果沒有網路,就會出現錯誤頁面;在LOAD_CACHE_ELSE_NETWORK模式下,無論是否有網路,只要本地有快取,都使用快取。本地沒有快取時才從網路上獲取。如果一個頁面的cache-control為max-age=60,在兩種模式下都使用本地快取資料。

提升H5在webview中的渲染速度,只是前端支援是不夠的,還需要客戶端採用合理的快取模式,詳細介紹點選這裡

微信小程式通訊模型

渲染和邏輯不在同一個環境中執行,邏輯層在純js環境中,渲染層交給了webview,所以wxml和wxss是在渲染層,這兩個執行緒的通訊會經由微信客戶端做中轉,邏輯層傳送網路請求也經由Native轉發。

高階前端進階系列 -  webview

理解渲染層

在安卓則是往 WebView 的 window 物件注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個相容層,主要提供了呼叫(invoke)和監聽(on)這兩種方法。開發者插入一個原生元件,一般而言,元件執行的時候被插入到 DOM 樹中,會呼叫客戶端介面,通知客戶端在哪個位置渲染一塊原生介面。在後續開發者更新元件屬性時,同樣地,也會呼叫客戶端提供的更新介面來更新原生介面的某些部分。

理解邏輯層

微信提供的js執行環境,因為對控制元件進行了自定義,因此這個沙箱環境不能有瀏覽器的介面,只提供js執行環境。

js執行環境採用:在iOS下是用內建的 JavaScriptCore框架,在安卓則是用騰訊x5核心提供的JsCore環境。

為什麼不使用web渲染?

如果採用純web技術渲染小程式,在複雜業務場景必然帶來效能問題。因為UI渲染跟js指令碼都在一個單執行緒中執行,就容易導致js邏輯任務搶佔UI資源。


本文參考:

美團技術團隊:tech.meituan.com/2017/06/09/…

51NB:mp.weixin.qq.com/s/BjKeh7gk-…

騰訊Bugly:blog.csdn.net/Tencent_Bug…



相關文章