摘要:本篇講講WebViewJavascriptBridge的基本原理及詳細(xì)講講如何去使用沪停,包括iOS端的使用和JS端的使用。原文地址(https://yq.aliyun.com/articles/37067)
一据某、前言
經(jīng)過(guò)多番百度墩朦、Google勺阐,發(fā)現(xiàn)WebViewJavascriptBridge的資源講解不是翻譯官方文檔就是直接說(shuō)官方提供的demo。但是筆者在寫這個(gè)demo時(shí)也遇到了不少問(wèn)題盾似,也想看看大家是怎么使用的敬辣,特別是JS端,弄了好久都沒(méi)有回調(diào)零院,原來(lái)是因?yàn)閘og溉跃。寫下本篇文章,希望大家少走彎路吧告抄!
二撰茎、iOS與H5交互的方案
縱觀所有iOS與H5交互的方案,有以下幾種:
◆ 第一種:有很多的app直接使用在webview的代理中通過(guò)攔截的方式與 native進(jìn)行交互打洼,通常是通過(guò)攔截url scheme判斷是否是我們需要攔截處理的url及其所對(duì)應(yīng)的要處理的功能是什么龄糊。任意版本都支持。
◆ 第二種:iOS7之后出了JavaScriptCore.framework用于與JS交互募疮,但是不支持iOS6炫惩,對(duì)于還需要支持iOS6的app,就不能考慮這個(gè)了阿浓。若需要了解他嚷,看最后的推薦閱讀。
◆ 第三種:WebViewJavascriptBridge開源庫(kù)使用,本質(zhì)上筋蓖,它也是通過(guò)webview的代理攔截scheme壮韭,然后注入相應(yīng)的JS肢娘。
◆ 第四種:react-native劣像,Weex等侯繁。
本篇文章專講講WebViewJavascriptBridge辛臊。
三滔吠、WebViewJavascriptBridge的基本原理
核心:
1烂完、兩個(gè)核心類WebViewJavascriptBridge赂蕴、WebViewJavascriptBridgeBase和一個(gè)注入的js文本類WebViewJavascriptBridge_JS(也可以選擇不注入设哗,自己封裝唱捣,在線引用js都可以);
2网梢、開源WebViewJavascriptBridge的所有封裝依賴最基礎(chǔ)兩點(diǎn):一是JS那邊WVJBIframe.src 變動(dòng)震缭,就會(huì)被當(dāng)前代理函數(shù)截獲;二是iOS的stringByEvaluatingJavaScriptFromString函數(shù)運(yùn)用战虏〖鹪祝基于這兩點(diǎn),運(yùn)用相關(guān)代理和回調(diào)實(shí)現(xiàn)功能的整體封裝烦感。
我們看看WebViewJavascriptBridge.m中Webview代理攔截的代碼:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
// JS那邊WVJBIframe.src 變動(dòng)巡社,就會(huì)被當(dāng)前代理函數(shù)截獲
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
// 如果是WebViewJavascriptBridge發(fā)送或者接收的消息,則按一下判斷進(jìn)行處理手趣。否則按照正常代理流程(代理如果在webview實(shí)例類實(shí)現(xiàn)的話)晌该。
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
// 1、第一次注入js代碼(此處可以做開關(guān)绿渣,不一定用注入JS朝群,也可以寫成單獨(dú)js被web端引用)
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
// 2、處理js返回給oc的消息中符,以及oc根據(jù)消息的callbackId將callback回傳js
// --js傳遞給原生的message來(lái)源于js的_fetchQueue()的函數(shù)返回姜胖,ios的stringByEvaluatingJavaScriptFromString函數(shù)運(yùn)用取到messageQueueString
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
// --具體處理js返回給oc的消息,以及oc根據(jù)消息的callbackId將callback回傳js的函數(shù)
[_base flushMessageQueue:messageQueueString];
} else {
// 3淀散、處理其他未知返回消息
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
在攔截后右莱,通過(guò)先通過(guò)-isBridgeLoadedURL:方法判斷URL是否是需要bridge的URL,若是吧凉,則通過(guò)injectJavascriptFile方法注入JS隧出;否則判斷URL是否是隊(duì)列消息,若是阀捅,則執(zhí)行查詢命令JS并刷新消息隊(duì)列胀瞪;最后,URL被識(shí)別為未知的消息。
四凄诞、JS端如何使用
下面是本demo的HTML完整代碼:
<!doctype html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style type='text/css'>
html { font-family:Helvetica; color:#222; }
h1 { color:steelblue; font-size:24px; margin-top:24px; }
button { margin:0 3px 10px; font-size:12px; }
.logLine { border-bottom:1px solid #ccc; padding:4px 2px; font-family:courier; font-size:11px; }
</style>
</head>
<body>
<h1>WebViewJavascriptBridge Demo</h1>
<script>
window.onerror = function(err) {
log('window.onerror: ' + err)
}
/*這段代碼是固定的圆雁,必須要放到j(luò)s中*/
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 = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
/*與OC交互的所有JS方法都要放在此處注冊(cè),才能調(diào)用通過(guò)JS調(diào)用OC或者讓OC調(diào)用這里的JS*/
setupWebViewJavascriptBridge(function(bridge) {
var uniqueId = 1
function log(message, data) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = 'logLine'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) {
log.insertBefore(el, log.children[0])
} else {
log.appendChild(el)
}
}
/* Initialize your app here */
/*我們?cè)谶@注冊(cè)一個(gè)js調(diào)用OC的方法帆谍,不帶參數(shù)伪朽,且不用ObjC端反饋結(jié)果給JS:打開本demo對(duì)應(yīng)的博文*/
bridge.registerHandler('openWebviewBridgeArticle', function() {
log("openWebviewBridgeArticle was called with by ObjC")
})
/*JS給ObjC提供公開的API,在ObjC端可以手動(dòng)調(diào)用JS的這個(gè)API汛蝙。接收ObjC傳過(guò)來(lái)的參數(shù)烈涮,且可以回調(diào)ObjC*/
bridge.registerHandler('getUserInfos', function(data, responseCallback) {
log("Get user information from ObjC: ", data)
responseCallback({'userId': '123456', 'blog': '標(biāo)哥的技術(shù)博客'})
})
/*JS給ObjC提供公開的API,ObjC端通過(guò)注冊(cè)窖剑,就可以在JS端調(diào)用此API時(shí)坚洽,得到回調(diào)。ObjC端可以在處理完成后西土,反饋給JS讶舰,這樣寫就是在載入頁(yè)面完成時(shí)就先調(diào)用*/
bridge.callHandler('getUserIdFromObjC', function(responseData) {
log("JS call ObjC's getUserIdFromObjC function, and js received response:", responseData)
})
document.getElementById('blogId').onclick = function (e) {
log('js call objc: getBlogNameFromObjC')
bridge.callHandler('getBlogNameFromObjC', {'blogURL': 'http://www.henishuo.com'}, function(response) {
log('JS got response', response)
})
}
})
</script>
<div id='buttons'></div> <div id='log'></div>
<div>
<input type="button" value="getBlogNameFromObjC" id="blogId"/>
</div>
</body>
</html>
在JS端,嵌入步驟是:
◆ 第一步:將下面的代碼放在JS中:
/*這段代碼是固定的需了,必須要放到j(luò)s中*/
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 = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
這段代碼是固定的跳昼,必須要放到j(luò)s中。
◆ 第二步:在下面的方法體里寫相關(guān)JS代碼:
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
所有與iOS交互的JS代碼放這里肋乍!
})
①JS如何調(diào)用iOS代碼
通過(guò)bridge.callHandler來(lái)調(diào)用:
bridge.callHandler('getBlogNameFromObjC',
{'blogURL': 'http://www.henishuo.com'},
function callback(response) {
log('JS got response', response)
}
})
其中,各參數(shù)說(shuō)明如下:
◆getBlogNameFromObjC:是iOS端register的handleName住拭,在iOS端注冊(cè)后,JS就可以直接通過(guò)這個(gè)handleName與iOS交互滔岳。比如,當(dāng)點(diǎn)擊某個(gè)按鈕時(shí)谱煤,就執(zhí)行上面這一段代碼摊求,結(jié)果就是js端將參數(shù)傳給了iOS端,iOS端收到參數(shù)刘离,然后通過(guò)回調(diào)給js,js會(huì)收到response硫惕,然后打印出來(lái)
◆{‘blogURL’: ‘http://www.henishuo.com’}:這是JSON字符串,傳到iOS端會(huì)被WebViewJavascriptBridge自動(dòng)轉(zhuǎn)換成id對(duì)象恼除,然后在回調(diào)處看到的就是字典對(duì)象了踪旷。
◆callback:這是個(gè)js函數(shù)名曼氛,在iOS端收到回調(diào)后,拿到了參數(shù)令野,然后通過(guò)閉包回調(diào)反饋給js端,這個(gè)反饋就是通過(guò)callback函數(shù)來(lái)傳值給js气破。
②JS端加入WebViewJavascriptBridge代碼注意事項(xiàng)
如果在下面的函數(shù)體內(nèi)有任何錯(cuò)誤,都不會(huì)有打印日志现使,也不會(huì)有任何回調(diào):
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
所有與iOS交互的JS代碼放這里!
})
因此朴下,如果遇到什么也沒(méi)有輸出,說(shuō)明你寫錯(cuò)了殴胧。另外佩迟,上面有demo中团滥,log函數(shù)是自定義的报强,不是系統(tǒng)的灸姊,因此如果沒(méi)有加入這個(gè)函數(shù)的定義秉溉,調(diào)用它也會(huì)導(dǎo)致不能交互。
五召嘶、iOS端如何使用
◆第一步:開啟日志
// 開啟日志,方便調(diào)試
[WebViewJavascriptBridge enableLogging];
◆第二步:給ObjC與JS建立橋梁
// 給哪個(gè)webview建立JS與OjbC的溝通橋梁
self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
// 設(shè)置代理弄跌,如果不需要實(shí)現(xiàn),可以不設(shè)置
[self.bridge setWebViewDelegate:self];
◆第三步:注冊(cè)HandleName铛只,用于給JS端調(diào)用iOS端
原生通過(guò)registerHandler方法注冊(cè)被調(diào)用的方法,js端通過(guò)callHandler方法注冊(cè)需要在原始端調(diào)用的方法淳玩。
// JS主動(dòng)調(diào)用OjbC的方法
// 這是JS會(huì)調(diào)用getUserIdFromObjC方法,這是OC注冊(cè)給JS調(diào)用的
// JS需要回調(diào)蜕着,當(dāng)然JS也可以傳參數(shù)過(guò)來(lái)。data就是JS所傳的參數(shù),不一定需要傳
// OC端通過(guò)responseCallback回調(diào)JS端豹芯,JS就可以得到所需要的數(shù)據(jù)
[self.bridge registerHandler:@"getUserIdFromObjC" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"js call getUserIdFromObjC, data from js is %@", data);
if (responseCallback) {
// 反饋給JS
responseCallback(@{@"userId": @"123456"});
}
}];
[self.bridge registerHandler:@"getBlogNameFromObjC" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"js call getBlogNameFromObjC, data from js is %@", data);
if (responseCallback) {
// 反饋給JS
responseCallback(@{@"blogName": @"哥的技術(shù)博客"});
}
}];
◆第四步:直接調(diào)用JS端注冊(cè)的HandleName
原生通過(guò)callHandler注冊(cè)需要調(diào)js的方法驱敲,js端通過(guò)registerHandler方法注冊(cè)被調(diào)用的方法铁蹈。
[self.bridge callHandler:@"getUserInfos" data:@{@"name": @"哥"} responseCallback:^(id responseData) {
NSLog(@"from js: %@", responseData);
}];
六、源代碼
大家可以到GITHUB下載源代碼:WebViewJavascriptBridgeDemo 众眨、WebViewJavascriptBridge