在APP加載的網(wǎng)頁中嗽仪,我們需要用到原生與JS 交互锡垄。本人做過的項(xiàng)目里有兩種實(shí)現(xiàn)方式:A破镰、用的是系統(tǒng)原生的方式實(shí)現(xiàn)。B默辨、用的是第三方寫的一個(gè)類庫WebViewJavascriptBridge
. 先就兩種方式進(jìn)行對(duì)比總結(jié)德频。
A、系統(tǒng)原生的方式實(shí)現(xiàn)
// 1. webview調(diào)用JS函數(shù), JS代碼可根據(jù)需要拼接好缩幸。
NSString *JSFunc = xxx;
[self.webView evaluateJavaScript:JSFunc completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"evaluateJavaScript:\n result = %@ error = %@",result, error);
}];
// 2. 網(wǎng)頁JS調(diào)原生:
// 1> 需要先設(shè)置Webview. config 的WKUserContentController
// 2> 注冊(cè)方法名 [userCC addScriptMessageHandler:self name:];
// 3> 遵守協(xié)議<WKScriptMessageHandler>壹置,實(shí)現(xiàn)其方法.
// 4> 在控制器銷毀時(shí),需要移除方法名注冊(cè)
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *userCC = [WKUserContentController new];
config.userContentController = userCC;
//JS調(diào)用OC 添加處理腳本
[userCC addScriptMessageHandler:self name:JSMessageName_Register];
WKWebView *webView = [[WKWebView alloc] initWithFrame:webFrame configuration:config];
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:JSMessageName_Register]) {
RegisterViewController *controller = [RegisterViewController new];
[self.navigationController pushViewController:controller animated:YES];
}
}
- (void)dealloc
{
WKUserContentController *userCC = self.webView.configuration.userContentController;
[userCC removeScriptMessageHandlerForName:JSMessageName_Register]; // 不移除表谊,怕是會(huì)造成webview無法釋放
}
這種方式下H5端該怎么做钞护?
使用原生的交互方式的話,安卓端的交互不一致爆办,需要H5端自己判斷难咕,比如:
//android終端或者uc瀏覽器
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
//ios終端
var isiOS = !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X)/);
if(isiOS){
// 假設(shè)方法名為: JSMessageName;postMessage里面可以傳參數(shù)
window.webkit.messageHandlers.JSMessageName.postMessage(['13300001111', 'Go Climbing This Weekend !!!'])
}else if(isAndroid){
.....
}
B、WebViewJavascriptBridge的方式
一余佃、使用方式
// 1.JS調(diào)用原生:假設(shè)方法名是getUserInfo
[_bridge registerHandler:@"getUserInfo" handler:^(id data, WVJBResponseCallback responseCallback) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"custName"] = [UserModel shareUserModel].custName;
dict[@"idCard"] = [UserModel shareUserModel].certifino;
responseCallback([JSONKit jsonStringWithObject:dict]);
}];
// 2. webview調(diào)用JS: 假設(shè)JS的方法名是redirect
[_bridge callHandler:@"redirect" data:@"test" responseCallback:^(id responseData) {
NSLog(@"%@", responseData);
}];
通過方法名與對(duì)應(yīng)的操作放在一起的方式更有利于維護(hù)暮刃;除此之外,這個(gè)提供了一份JS模板文件爆土,安卓端也可以是同樣的原理實(shí)現(xiàn)沾歪。
二、實(shí)現(xiàn)原理
webview調(diào)用JS的原理
1.使用方法-(void)callHandler:data:responseCallback: 傳遞方法名雾消、參數(shù)、block挫望。
-> 2. 內(nèi)部用字典將 block保存立润,然后將方法名、參數(shù)媳板、保存block對(duì)應(yīng)的key這些信息按照J(rèn)S模板拼裝好JS代碼桑腮。
-> 3. 回調(diào)到主線程中使用[self.webView evaluateJavaScript:completionHandler:];方法調(diào)用JS。
-> 4. H5端收到這個(gè)調(diào)用之后蛉幸,調(diào)用iframe實(shí)現(xiàn)對(duì)約定好的地址跳轉(zhuǎn)CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
//iframe可以理解為webview中的窗口破讨,當(dāng)我們改變iframe的src屬性的時(shí)候,相當(dāng)于我們?yōu)g覽器實(shí)現(xiàn)了鏈接的跳轉(zhuǎn)奕纫。比如從www.baidu.com跳轉(zhuǎn)到www.google.com提陶。下面這段代碼的目的就是實(shí)現(xiàn)一個(gè)到https://__bridge_loaded__的跳轉(zhuǎn)
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(WVJBIframe);
-> 5. 在跳轉(zhuǎn)時(shí),我們的webview會(huì)收到代理方法回調(diào), 在這里做出對(duì)URL
判斷是否是我們約定好的協(xié)議頭匹层,如果是約定好的JS與原生交互的就進(jìn)入交互處理隙笆。
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
if ([_base isCorrectProcotocolScheme:url]) {// 是否是約定好的協(xié)議頭
if ([_base isBridgeLoadedURL:url]) {// 是否是將橋接JS代碼加載到網(wǎng)頁的對(duì)應(yīng)的url
[_base injectJavascriptFile];// js代碼注入
} else if ([_base isQueueMessageURL:url]) {// 是否是JS與原生交互的url
NSLog(@"JS與原生交互原理的地方");
[self WKFlushMessageQueue];
} else {// 是約定好的協(xié)議頭,但是未知的url
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);// 因?yàn)檫@里是處理交互不需要加載
return;
}
......
}
-> 6. 主動(dòng)調(diào)用JS代碼升筏,從而獲取響應(yīng)的數(shù)據(jù)result
.
NSString *js = @"WebViewJavascriptBridge._fetchQueue();";
[_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) {
[_base flushMessageQueue:result];
}];
-> 7.對(duì)響應(yīng)的數(shù)據(jù)result
進(jìn)行處理, 從中得到我們保存在字典中相應(yīng)的block調(diào)用( _responseCallbacks[responseId]
)撑柔。
id messages = [self _deserializeMessageJSON: result];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
}
JS調(diào)用原生的原理
- JS調(diào)用原生時(shí),首先我們會(huì)在webview的代理方法里面收到您访,還是原來的方法, 判斷是否是交互的操作跟上面的一樣, 下面是縮減后的代碼铅忿。
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
if ([_base isQueueMessageURL:url]) {// 是否是JS與原生交互的url
NSLog(@"JS與原生交互原理的地方");
[self WKFlushMessageQueue];
decisionHandler(WKNavigationActionPolicyCancel);
}
}
-> 2. 主動(dòng)調(diào)用JS代碼,從而獲取響應(yīng)的數(shù)據(jù)result
.
NSString *js = @"WebViewJavascriptBridge._fetchQueue();";
[_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) {
[_base flushMessageQueue:result];
}];
-> 3. 對(duì)這個(gè)result
進(jìn)行處理灵汪。
- (void)flushMessageQueue:(NSString *)messageQueueString{
id messages = [self _deserializeMessageJSON:messageQueueString];
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
// 調(diào)用原生方法時(shí)的參數(shù)中有 callbackId檀训,則需要我們?cè)谑褂脮r(shí)的那個(gè)block中手動(dòng)調(diào)用這個(gè)responseCallback。
responseCallback = ^(id responseData) {
if (responseData == nil) { responseData = [NSNull null]; }
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];// 里面會(huì)再次使weview調(diào)用js給網(wǎng)頁傳遞數(shù)據(jù)‘ responseData’
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
//self.messageHandlers的來源: - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler享言。
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
handler(message[@"data"], responseCallback);
}
附:第三種方式:
OC與JS交互之JavaScriptCore