作过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native。而后经过Bridge技术的js调用本地的库。javascript
在讲JSBridge技术以前。咱们来看一下传统的实现方式。html
native调用js比較简单,仅仅要遵循:”javascript: 方法名(‘參数,需要转为字符串’)”的规则就能够。前端
在4.4以前,调用的方式:java
// mWebView = new WebView(this);
mWebView.loadUrl("javascript: 方法名('參数,需要转为字符串')");
//ui线程中运行
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript: 方法名('參数,需要转为字符串')");
Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();
}
});
4.4之后(包含4.4)。使用下面方式:android
mWebView.evaluateJavascript("javascript: 方法名('參数,需要转为字符串')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为相应JS方法的返回值
}
});
说明:ios
Js调用Native需要对WebView设置@JavascriptInterface注解,这里有个漏洞。后面会给你们说明。git
要想js可以Native,需要对WebView设置下面属性。github
WebSettings webSettings = mWebView.getSettings();
//Android容器赞成JS脚本
webSettings.setJavaScriptEnabled(true);
//Android容器设置侨连对象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");
这里咱们看到了getJSBridge()。Native中经过addJavascriptInterface加入暴露出来的JS桥对象,而后再该对象内部声明相应的API方法。web
private Object getJSBridge(){
Object insertObj = new Object(){
@JavascriptInterface
public String foo(){
return "foo";
}
@JavascriptInterface
public String foo2(final String param){
return "foo2:" + param;
}
};
return insertObj;
}
那么Html怎么调用Native的方法呢?api
//调用方法一
window.JSBridge.foo(); //返回:'foo'
//调用方法二
window.JSBridge.foo2('test');//返回:'foo2:test'
说明:
注:说到WebView中接口隐患的问题,这里你们可以參考WebViw漏洞利用,只是Android发展到现在,这个漏洞基本没有了。
Native调用js的方法比較简单。Native经过stringByEvaluatingJavaScriptFromString调用Html绑定在window上的函数。只是应注意Oc和Swift的写法。
//Swift
webview.stringByEvaluatingJavaScriptFromString("方法名(參数)")
//OC
[webView stringByEvaluatingJavaScriptFromString:@"方法名(參数);"];
说明:
Native中经过引入官方提供的JavaScriptCore库(iOS7以上),而后可以将api绑定到JSContext上(而后Html中JS默认经过window.top.*可调用)。
引入官方的库文件
#import <JavaScriptCore/JavaScriptCore.h>
Native注冊api函数(OC)
-(void)webViewDidFinishLoad:(UIWebView *)webView{
[self hideProgress];
[self setJSInterface];
}
-(void)setJSInterface{
JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 注冊名为foo的api方法
context[@"foo"] = ^() {
//获取參数
NSArray *args = [JSContext currentArguments];
NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
//作一些本身的逻辑
//返回一个值 'foo:'+title
return [NSString stringWithFormat:@"foo:%@", title];
};
}
Html中JS调用Native方法
window.top.foo('test');
说明:
JSBridge:听其取名就是js和Native以前的桥梁,而实际上JSBridge确实是JS和Native以前的一种通讯方式。简单的说,JSBridge就是定义Native和JS的通讯,Native仅仅经过一个固定的桥对象调用JS,JS也仅仅经过固定的桥对象调用Native。JSBridge还有一个叫法及你们熟知的Hybrid app技术。
流程:H5->经过某种方式触发一个url->Native捕获到url,进行分析->原生作处理->Native调用H5的JSBridge对象传递回调。
咱们前面讲过了原生的WebView/UIWebView控件已经可以和Js实现数据通讯了。那为何还要JSBridge呢?
事实上使用JSBridge有很是多方面的考虑:
url scheme是一种类似于url的连接,是为了方便app直接互相调用设计的。详细来说假设是系统的url scheme,则打开系统应用,不然找看是否有app注冊这样的scheme,打开相应app。
注:这样的scheme必须原生app注冊后才会生效。
而在咱们实际的开发中,app不会注冊相应的scheme,而是由前端页面经过某种方式触发scheme(如用iframe.src),而后Native用某种方法捕获相应的url触发事件,而后拿到当前的触发url,依据定义好的协议,分析当前触发了那种方法。
要实现JSBridge,咱们需要按下面步骤分析:
JSBridge的完整流程可总结为:
咱们规定,JS和Native之间的通讯必须经过一个H5全局对象JSbridge来实现。该对象有例如如下特色:
该对象名为”JSBridge”,是H5页面中全局对象window的一个属性。形如:
var JSBridge = window.JSBridge || (window.JSBridge = {});
该对象有例如如下方法:
调用后会将方法注冊到本地变量messageHandlers 中。
咱们定义好了全局桥对象,可以经过它的callHandler方法来调用原生的api。
在运行callHandler时,内部经历了下面步骤:
//url scheme的格式如
//基本实用信息就是后面的callbackId,handlerName与data
//原生捕获到这个scheme后会进行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
//建立隐藏iframe过程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);
//触发scheme
messagingIframe.src = uri;
注:正常来讲是可以经过window.location.href达到发起网络请求的效果的。但是有一个很是严重的问题,就是假设咱们连续屡次改动window.location.href的值,在Native层仅仅能接收到最后一次请求,前面的请求都会被忽略掉。
因此JS端发起网络请求的时候,需要使用iframe。这样就可以避免这个问题。
上一步。咱们已经成功在H5页面中触发scheme,那么Native怎样捕获scheme被触发呢?
依据系统不一样,Android和iOS分别有本身的处理方式。
在Android中(WebViewClient里),经过shouldoverrideurlloading可以捕获到url scheme的触发。
public boolean shouldOverrideUrlLoading(WebView view, String url){
//假设返回false。则WebView处理连接url。假设返回true,表明WebView依据程序来运行url
return true;
}
iOS中,UIWebView有个特性:在UIWebView内发起的所有网络请求,都可以经过delegate函数在Native层获得通知。
这样,咱们可以在webview中捕获url scheme的触发(原理是利用 shouldStartLoadWithRequest)
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = [request URL];
NSString *requestString = [[request URL] absoluteString];
//获取利润url scheme后自行进行处理
在前面的步骤中,Native已经接收到了JS调用的方法,那么接下来,原生就应该依照定义好的数据格式来解析数据了,Native接收到Url后,可以依照这样的格式将回调參数id、api名、參数提取出来,而后按例如如下步骤进行。
到了这一步,就该Native经过JSBridge调用H5的JS方法或者通知H5进行回调了。当中的messageJSON数据格式依据两种不一样的类型。
JSBridge._handleMessageFromNative(messageJSON);
Native通知H5页面进行回调:
数据格式为: Native通知H5回调的JSON格式。
Native主动调用H5方法:
Native主动调用H5方法时,数据格式是:{handlerName:api名,data:数据,callbackId:回调id}:
前面有提到Native主动调用H5中注冊的api方法,那么h5中怎么注冊供原生调用的api方法呢?
JSBridge.registerHandler('testH5Func',function(data,callback){
alert('測试函数接收到数据:'+JSON.stringify(data));
callback&&callback('測试回传数据...');
});
如上代码,当中第一个data即原生传过来的数据,第二个callback是内部封装过一次的,运行callback后会触发url scheme,通知原生获取回调信息.
github上有一个开源项目,它里面的JSBridge作法在iOS上进一步优化了,因此參考他的作法,这里进一步进行了无缺。地址marcuswestin/WebViewJavascriptBridge
JSBridge对象图解:
JSBridge实现完整流程:
那么咱们在实际的开发中,怎样针对Android和iOS的不一样状况,统一出一种完整的方案。
前面提到的JSBridge都是基于url scheme的,但事实上假设不考虑Android4.2下面,iOS7下面,事实上也可以用还有一套方案的。
OS中,原生经过JavaScriptCore里面的方法来注冊一个统一api,其他和Android中同样。