JSBridge 解决通信
JSBridge是Native代码与JS代码的通信桥梁。
其大致过程是: H5触发url scheme->Native捕获url scheme->原生分析,执行->原生调用h5
url scheme介绍
url scheme是一种类似于url的链接,是为了方便app直接互相调用设计的
具体为,可以用系统的OpenURI打开一个类似于url的链接(可拼入参数),然后系统会进行判断,如果是系统的url scheme,则打开系统应用,否则找看是否有app注册这种scheme,打开对应app 需要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为(weixin://) 而本文JSBridge中的url scheme则是仿照上述的形式的一种方式
具体为,app不会注册对应的scheme,而是由前端页面通过某种方式触发scheme(如用iframe.src),然后Native用某种方法捕获对应的url触发事件,然后拿到当前的触发url,根据定义好的协议,分析当前触发了那种方法,然后根据定义来执行等
注意,iOS10以后,urlscheme必须符合url规范,否则会报错
使用方式
- html 中增加以下代码
// 这段代码是固定的,必须要放到js中
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)
}
注册事件 给原生用
bridge.registerHandler('alertMessage', function(data, responseCallback) {
alert(data)
// 把处理好的结果返回给OC
responseCallback('alert succes')
});
调用原生事件
bridge.callHandler('openCamera', {'count':'10张'}, function responseCallback(responseData) {
console.log("OC中返回的参数:", responseData)
});
- ios 中pod install WebViewJavascriptBridge
// 给webView建立JS和OC的沟通桥梁
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
[self.bridge setWebViewDelegate:self];
JS调用OC的API:访问相册
[self.bridge registerHandler:@"openCamera" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"需要%@图片", data[@"count"]);
UIImagePickerController *imageVC = [[UIImagePickerController alloc] init];
imageVC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:imageVC animated:YES completion:nil];
}];
OC调用的JSAPI:弹窗
[self.bridge callHandler:@"alertMessage" data:@"调用了js中的Alert弹窗!" responseCallback:^(id responseData) {
}]; ## 1. 创建桥接对象WebViewJavascriptBridge
JS和Native通信须通过一个H5全局对象WebViewJavascriptBridge 来实现,该对象有如下特点
WebViewJavascriptBridge_JS.h
if (window.WebViewJavascriptBridge) {
return;
}
window.WebViewJavascriptBridge = {
registerHandler: registerHandler, // H5调用,注册本地JS方法,注册后Native可通过JSBridge调用
callHandler: callHandler, // H5调用,调用原生开放的api,调用后实际上还是本地通过url scheme触发。
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC // Native调用,原生调用H5页面注册的方法,或者通知H5页面执行回调
};
var messagingIframe; // iframe
var sendMessageQueue = []; // 消息队列
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'; // scheme
var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__';
var responseCallbacks = {};
var uniqueId = 1;
***
// 创建frame
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
2 JS 调用原生
js通过callHandler调用原生
内部实现原理: 1. 生成一个回调函数id,并将id和对应回调添加进入回调函数集合responseCallbacks中 2. 通过特定的_doSend(),将传入的数据,方法名一起,拼接成一个url scheme
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
sendMessageQueue.push(message);
//触发scheme
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
3 Native拦截api
Android捕获url scheme
在Android中(WebViewClient里),通过shouldoverrideurlloading可以拦截到url scheme的触发
public boolean shouldOverrideUrlLoading(WebView view, String url){
//读取到url后自行进行分析处理
//如果返回false,则WebView处理链接url,如果返回true,代表WebView根据程序来执行url
return true;
}
**IOS webview捕获url scheme **
在Ios中通过 delegate shouldStartLoadWithRequest 拦截url scheme的触发
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
//
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile]; // 注入jsbridage 在index.html 中必须有的一段代码出发
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_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;
}
}
#define kCustomProtocolScheme @"wvjbscheme"
#define kQueueHasMessage @"__WVJB_QUEUE_MESSAGE__"
#define kBridgeLoaded @"__BRIDGE_LOADED__"
// 是否有消息
-(BOOL)isQueueMessageURL:(NSURL*)url {
if([[url host] isEqualToString:kQueueHasMessage]){
return YES;
} else {
return NO;
}
}
// 是否是第一次加载完成
-(BOOL)isBridgeLoadedURL:(NSURL*)url {
return ([[url scheme] isEqualToString:kCustomProtocolScheme] && [[url host] isEqualToString:kBridgeLoaded]);
}
4. 解析 url-参数和回调的格式
- 原生 获取 url scheme 解析里面的 handlerName,data, callbackId,
- 原生本地执行对应的 handlerName 功能方法
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
iOS调用 h5
在 iOS端也有一个 JSBridge 对象 也有其相对应callHandler,registerHandler的方法和属性,但其核心代码其实就是将要执行的代码 通过字符串的方式 stringByEvaluatingJavaScriptFromString 就可以执行相应的代码
- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand
{
return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
}
我们完全可以执行任何JS代码:比如
[self.webView stringByEvaluatingJavaScriptFromString:@"window.alert(1)"];
WebViewJavascriptBridge.m
- (void)callHandler:(NSString *)handlerName data:(id)data {
[self callHandler:handlerName data:data responseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy]; }
WebViewJavascriptBridgeBase.h
@protocol WebViewJavascriptBridgeBaseDelegate <NSObject>
- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand;
@end
@interface WebViewJavascriptBridgeBase : NSObject
@property (strong, nonatomic) WVJBHandler messageHandler;
js中处理回调
function _dispatchMessageFromObjC(messageJSON) {
var message = JSON.parse(messageJSON);
// 原生的回掉 js
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// 其实就是用 _doSend 去掉原生的一个方法
***
if (message.callbackId) { //
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ responseId:callbackResponseId, responseData:responseData });
};
}
}
});
}
总结
Native 和 JS 就是通过一个js-bridge 对象进行交互
js 通过 url scheme 改变iframe的url,使webview拦截到这个 url scheme的变化,然后通过接些路径获取要调用的原生方法
而IOS调用js其实就是 [webview stringByEvaluatingJavaScriptFromString:@””];这个方法可以调用js中的方法
其他方式
Android中 addJavascriptInterface(但存在安全问题)