本文講述h5頁面跟原生通信,比如在app內(nèi),調(diào)用相機,獲取相冊內(nèi)的圖片,在app內(nèi)拉起微信小程序等等,h5頁面沒有這么多權(quán)限能夠直接調(diào)用移動端的原生能力,這個時候就需要與原生進行通訊,傳遞一個信號給原生這邊,然后原生直接調(diào)用手機端的能力。
下面分別講解h5與Android,ios系統(tǒng)通信
JS與Android
Android處理
其實h5能夠在app內(nèi)中存在,就是在app中有webview的存在,在webview中可以是一個h5的鏈接,這樣這個h5頁面就能夠app內(nèi)展現(xiàn)。
Android中,往webview里面注入WebViewJavascriptBridge.js
我們能夠看到在android往webview注入了一段js代碼,接下來我們看看這段代碼的邏輯。
這里往webview中的document注入事件webViewJavascriptBridgeReady,向window中添加一個對象WebViewJavascriptBridge。
注意這個WebViewJavascriptBridge對象中包含了init,send,registerHandler,callHandler,_handleMessageFormNative函數(shù)。
接下來看這個init函數(shù)中的邏輯
使用 iframe 創(chuàng)建消息隊列的主要原因是:
- 實現(xiàn)跨域通信。iframe 可以被嵌入到不同域的頁面中,使得父子頁面可以跨域通信。
- 解耦通信方。使用 iframe,可以使得父頁面和子頁面的 JavaScript 環(huán)境相互隔離。父頁面無法直接訪問子頁面的變量和函數(shù),只能通過消息通信。這減少了兩端的依賴,有利于后期維護。
- 兼容性好。iframe 比較老的技術(shù),廣泛支持各種瀏覽器,不需要擔(dān)心兼容性問題。
- 簡單易用。向 iframe 發(fā)送消息和監(jiān)聽消息非常簡單,不需要額外的庫或工具。
這里可以發(fā)現(xiàn),初始化就完成了創(chuàng)建消息隊列,初始化默認消息隊列,還有最重要的發(fā)送消息。
接下來就看h5怎么接收和處理這些通信了。
h5頁面處理
到這里,應(yīng)該也能猜到一些了,因為上面Android注入了一段js代碼,從中創(chuàng)建了webViewJavascriptBridgeReady事件,而且往window中添加了一個對象。
那h5這邊初始化也很簡單,就是判斷有沒有window.WebViewJavascriptBridge,有的話,就直接可以傳輸了,沒有的話,就需要監(jiān)聽webViewJavascriptBridgeReady事件,當這個事件執(zhí)行了,就可以進行通信了。
if (window.WebViewJavascriptBridge) {
//do your work here
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
//do your work here
},
false
);
}
而這個window.WebViewJavascriptBridge也就是常說的通訊橋。無論是h5調(diào)用原生方法,還是原生想要觸發(fā)h5方法,都是需要使用到通訊橋。
如果未定義 window.WebViewJavascriptBridge
,則將所有 JsBridge 函數(shù)調(diào)用放入 window.WVJBCallbacks
數(shù)組中,當觸發(fā) WebViewJavascriptBridgeReady
事件時,此任務(wù)隊列將被刷新。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback);
}
window.WVJBCallbacks = [callback];
}
調(diào)用 setupWebViewJavascriptBridge
,然后使用橋注冊處理程序或調(diào)用 Java 處理程序:
這里的registerHandler和callHandler應(yīng)該很熟悉,因為在Android中注入的js代碼中就有的這兩個方法,這兩個方法就在window.WebViewJavascriptBridge對象上。
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('JS to Android', function(data, responseCallback) {
console.log("Android received response:", data);
responseCallback(data);
});
bridge.callHandler('Android to JS', {'key':'value'}, function(responseData) {
console.log("JS received response:", responseData);
});
});
registerHandler方法是h5頁面?zhèn)鬟f給Android的數(shù)據(jù)
callHandler方法是Android傳遞給h5的數(shù)據(jù)
總結(jié)一下工作流程:
- JS端判斷是否有window.WebViewJavascriptBridge對象;
- 有的話就進入回調(diào),進行通信;
- 沒有的話就需要, 將所有 JsBridge 函數(shù)調(diào)用放入
window.WVJBCallbacks
數(shù)組中,當觸發(fā)WebViewJavascriptBridgeReady
事件時,此任務(wù)隊列將被刷新;
JS與IOS
IOS處理
在ios處理中,需要介紹一下WebViewJavascriptBridge
WebViewJavascriptBridge 是用于在 WKWebView,UIWebView 和 WebView 中的 Obj-C 和 JavaScript 之間發(fā)送消息的 iOS / OSX 橋接器。
這里本質(zhì)跟Android是一樣的,也是注入了一段js代碼,然后注入到webview組件中,實現(xiàn)原生與js的交互。
我們先看看js處理的邏輯
這里跟Android還是很像,往window中注入一個通訊橋,通訊橋的名稱是WebViewJavascriptBridge,然后在通信橋上有registerHandler,callHandler,disableJavscriptAlertBoxSafetyTimeout,_fetchQueue,_handleMessageFromObjC等方法。
接下來看一下如何往webview中注入的
與上面的Android不同的是,這是ObjC語言編寫的,Android是Java語言。
// WKNavigationDelegate 協(xié)議方法,用于監(jiān)聽 Request 并決定是否允許導(dǎo)航
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
// webView 校驗
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
// 核心代碼
if ([_base isWebViewJavascriptBridgeURL:url]) { // 判定 WebViewJavascriptBridgeURL
if ([_base isBridgeLoadedURL:url]) { // 判定 BridgeLoadedURL
// 注入 JS 代碼
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) { // 判定 QueueMessageURL
// 刷新消息隊列
[self WKFlushMessageQueue];
} else {
// 記錄未知 bridge msg 日志
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
// 調(diào)用 _webViewDelegate 對應(yīng)的代理方法
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
h5頁面處理
這里跟Android處理不同,啟動通訊橋時,在h5頁面也需要創(chuàng)建一個iframe標簽來進行通信
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
// 創(chuàng)建一個 iframe
var WVJBIframe = document.createElement('iframe');
// 設(shè)置 iframe 為不顯示
WVJBIframe.style.display = 'none';
// 將 iframe 的 src 置為 'https://__bridge_loaded__'
WVJBIframe.src = 'https://__bridge_loaded__';
// 將 iframe 加入到 document.documentElement
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
Note: 假 Request 的發(fā)起有兩種方式,-1:location.href
-2:iframe
。通過 location.href
有個問題,就是如果 JS 多次調(diào)用原生的方法也就是 location.href
的值多次變化,Native 端只能接受到最后一次請求,前面的請求會被忽略掉,所以這里 WebViewJavascriptBridge 選擇使用 iframe。
因為加入了 src 為 https://__bridge_loaded__
的 iframe 元素,我們上面截獲 url 的代理方法就會拿到一個 https://__bridge_loaded__
的 url,由于 https 滿足判定 WebViewJavascriptBridgeURL,將會進入核心代碼區(qū)域接著會被判定為 BridgeLoadedURL 執(zhí)行注入 JS 代碼的方法,即 [_base injectJavascriptFile];
。
這樣就形成了初始化橋,與android中的將所有 JsBridge 函數(shù)調(diào)用放入 window.WVJBCallbacks
數(shù)組中,后面要處理的邏輯一樣了。
- (void)injectJavascriptFile {
// 獲取到 WebViewJavascriptBridge_JS 的代碼
NSString *js = WebViewJavascriptBridge_js();
// 將獲取到的 js 通過代理方法注入到當前綁定的 WebView 組件
[self _evaluateJavascript:js];
// 如果當前已有消息隊列則遍歷并分發(fā)消息,之后清空消息隊列
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
}
最后,調(diào)用 setupWebViewJavascriptBridge
,然后使用橋注冊處理程序并調(diào)用 ObjC 處理程序:
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
bridge.registerHandler('JS to IOS', function(data, responseCallback) {
console.log("IOS received response:", data)
responseCallback(data)
})
bridge.callHandler('IOS to JS', {'key':'value'}, function responseCallback(responseData) {
console.log("JS received response:", responseData)
})
})
最后總結(jié)一下WebViewJavascriptBridge 的工作流:
- JS 端加入 src 為
https://__bridge_loaded__
的 iframe - Native 端檢測到 Request,檢測如果是
__bridge_loaded__
則通過當前的 WebView 組件注入 WebViewJavascriptBridge_JS 代碼 - 注入代碼成功之后會加入一個 messagingIframe,其 src 為
https://__wvjb_queue_message__
- 之后不論是 Native 端還是 JS 端都可以通過
registerHandler
方法注冊一個兩端約定好的 HandlerName 的處理,也都可以通過callHandler
方法通過約定好的 HandlerName 調(diào)用另一端的處理(兩端處理消息的實現(xiàn)邏輯對稱)
看到這里,其實Android和IOS系統(tǒng)最大的不同,相信大家已經(jīng)感受到了,那就處理流程順序不太一樣。
但其實在公司的項目里面,上面這些知識只是基礎(chǔ),而公司內(nèi),都會制定一套完整的sdk來,各個端對齊,而sdk中,也不是簡單的通信,還會有各種兼容,比如當前運行的系統(tǒng),是否在指定的app內(nèi),app的版本信息,各個通信函數(shù)是否支持最低的版本號…文章來源:http://www.zghlxwxcb.cn/news/detail-586633.html
如果需要封裝自己項目中的webview組件,還需要另外實現(xiàn)HTTP cookie注入,自定義User-Agent,白名單或者權(quán)限校驗等等功能,更進一步還需要對webview組件進行初始化速度,頁面渲染速度以及頁面緩存策略的優(yōu)化。文章來源地址http://www.zghlxwxcb.cn/news/detail-586633.html
到了這里,關(guān)于h5頁面如何與原生交互的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!