作過混合開發的人都知道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中同樣。