JSBridge深度剖析

概述

作過混合開發的人都知道Ionic和PhoneGap之類的框架,這些框架在web基礎上包裝一層Native。而後經過Bridge技術的js調用本地的庫。javascript

在講JSBridge技術以前。咱們來看一下傳統的實現方式。html

Android端

Native調JS

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

  • 4.4以前Native經過loadUrl來調用JS方法,僅僅能讓某個JS方法運行,但是沒法獲取該方法的返回值
  • 4.4以後,經過evaluateJavascript異步調用JS方法,並且能在onReceiveValue中拿到返回值
  • 不適合傳輸大量數據(大量數據建議用接口方式獲取)
  • mWebView.loadUrl(「javascript: 方法名(‘參數,需要轉爲字符串’)」);函數需在UI線程運行,因爲mWebView爲UI控件

JS調Native

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'

說明:

  • 在Android4.2以上(api17後),暴露的api要加上註解@JavascriptInterface,不然會找不到方法。

  • 在api17曾經,addJavascriptInterface有風險,hacker可以經過反編譯獲取Native註冊的Js對象。而後在頁面經過反射Java的內置 靜態類。獲取一些敏感的信息和破壞
  • JS調用Native暴露的api,並且能獲得相應返回值

注:說到WebView中接口隱患的問題,這裏你們可以參考WebViw漏洞利用,只是Android發展到現在,這個漏洞基本沒有了。

iOS端

Native調JS

Native調用js的方法比較簡單。Native經過stringByEvaluatingJavaScriptFromString調用Html綁定在window上的函數。只是應注意Oc和Swift的寫法。

//Swift
webview.stringByEvaluatingJavaScriptFromString("方法名(參數)")
//OC
[webView stringByEvaluatingJavaScriptFromString:@"方法名(參數);"];

說明:

  • Native調用JS方法時,能拿到JS方法的返回值
  • 不適合傳輸大量數據(大量數據建議用接口方式獲取)

JS調Native

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');

說明:

  • iOS7纔出現這樣的方式,在這以前,js沒法直接調用Native,僅僅能經過JSBridge方式簡單介紹調用
  • JS能調用到已經暴露的api,並且能獲得相應返回值
  • iOS原生自己是沒法被JS調用的,但是經過引入官方提供的第三方」JavaScriptCore」,就能夠開放api給JS調用

JSBridge

什麼是JSBridge

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有很是多方面的考慮:

  • Android4.2下面,addJavascriptInterface方式有安全漏掉
  • iOS7下面,JS沒法調用Native
  • url scheme交互方式是一套現有的成熟方案,可以完美兼容各類版本號。對曾經老版本號技術的兼容。

url scheme

url scheme是一種類似於url的連接,是爲了方便app直接互相調用設計的。詳細來說假設是系統的url scheme,則打開系統應用,不然找看是否有app註冊這樣的scheme,打開相應app。
注:這樣的scheme必須原生app註冊後纔會生效。

而在咱們實際的開發中,app不會註冊相應的scheme,而是由前端頁面經過某種方式觸發scheme(如用iframe.src),而後Native用某種方法捕獲相應的url觸發事件,而後拿到當前的觸發url,依據定義好的協議,分析當前觸發了那種方法。

JSBridge技術實現

要實現JSBridge,咱們需要按下面步驟分析:

  • 第一步:設計出一個Native與JS交互的全局橋對象
  • 第二步:JS怎樣調用Native
  • 第三步:Native怎樣得知api被調用
  • 第四步:分析url-參數和回調的格式
  • 第五步:Native怎樣調用JS
  • 第六步:H5中api方法的註冊以及格式

JSBridge的完整流程可總結爲:
這裏寫圖片描寫敘述

設計Native與JS交互的全局橋對象

咱們規定,JS和Native之間的通訊必須經過一個H5全局對象JSbridge來實現。該對象有例如如下特色:
該對象名爲」JSBridge」,是H5頁面中全局對象window的一個屬性。形如:

var JSBridge = window.JSBridge || (window.JSBridge = {});

該對象有例如如下方法:

  • registerHandler( String,Function )H5調用註冊本地JS方法,註冊後Native可經過JSBridge調用。

    調用後會將方法註冊到本地變量messageHandlers 中。

  • callHandler( String,JSON,Function )H5調用 調用原生開放的api,調用後實際上仍是本地經過url scheme觸發。調用時會將回調id存放到本地變量responseCallbacks中
  • _handleMessageFromNative( JSON )Native調用 原生調用H5頁面註冊的方法,或者通知H5頁面運行回調方法

這裏寫圖片描寫敘述

JS調用Native

咱們定義好了全局橋對象,可以經過它的callHandler方法來調用原生的api。

callHandler函數內部實現過程

在運行callHandler時,內部經歷了下面步驟:

  1. 推斷是否有回調函數,假設有,生成一個回調函數id,並將id和相應回調加入進入回調函數集合responseCallbacks中。
  2. 經過特定的參數轉換方法,將傳入的數據,方法名一塊兒,拼接成一個url scheme
//url scheme的格式如
//基本實用信息就是後面的callbackId,handlerName與data
//原生捕獲到這個scheme後會進行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
  1. 使用內部早就建立好的一個隱藏iframe來觸發scheme
//建立隱藏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。這樣就可以避免這個問題。

Native通知api被調用

上一步。咱們已經成功在H5頁面中觸發scheme,那麼Native怎樣捕獲scheme被觸發呢?

依據系統不一樣,Android和iOS分別有本身的處理方式。

Android

在Android中(WebViewClient裏),經過shouldoverrideurlloading可以捕獲到url scheme的觸發。

public boolean shouldOverrideUrlLoading(WebView view, String url){
    //假設返回false。則WebView處理連接url。假設返回true,表明WebView依據程序來運行url
    return true;
}

iOS

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後自行進行處理

分析url-參數和回調的格式

在前面的步驟中,Native已經接收到了JS調用的方法,那麼接下來,原生就應該依照定義好的數據格式來解析數據了,Native接收到Url後,可以依照這樣的格式將回調參數id、api名、參數提取出來,而後按例如如下步驟進行。

  1. 依據api名,在本地找尋相應的api方法,並且記錄該方法運行完後的回調函數id
  2. 依據提取出來的參數,依據定義好的參數進行轉化
  3. 原生本地運行相應的api功能方法
  4. 功能運行完成後,找到此次api調用相應的回調函數id,而後連同需要傳遞的參數信息,組裝成一個JSON格式的參數
  5. 經過JSBridge通知H5頁面回調

Native調用JS

到了這一步,就該Native經過JSBridge調用H5的JS方法或者通知H5進行回調了。當中的messageJSON數據格式依據兩種不一樣的類型。

JSBridge._handleMessageFromNative(messageJSON);

Native通知H5頁面進行回調
數據格式爲: Native通知H5回調的JSON格式。
Native主動調用H5方法
Native主動調用H5方法時,數據格式是:{handlerName:api名,data:數據,callbackId:回調id}:

  • handlerName String型 需要調用的,h5中開放的api的名稱
  • data JSON型 需要傳遞的數據,固定爲JSON格式(因爲咱們固定H5中註冊的方法接收的第一個參數必須是JSON,第二個是回調函數)
  • callbackId String型 原生生成的回調函數id,h5運行完成後經過url scheme通知原生api成功運行,並傳遞參數

H5中api方法的註冊以及格式

前面有提到Native主動調用H5中註冊的api方法,那麼h5中怎麼註冊供原生調用的api方法呢?

JSBridge.registerHandler('testH5Func',function(data,callback){
    alert('測試函數接收到數據:'+JSON.stringify(data));
    callback&&callback('測試回傳數據...');
});

如上代碼,當中第一個data即原生傳過來的數據,第二個callback是內部封裝過一次的,運行callback後會觸發url scheme,通知原生獲取回調信息.

無缺JSBridge方案

github上有一個開源項目,它裏面的JSBridge作法在iOS上進一步優化了,因此參考他的作法,這裏進一步進行了無缺。地址marcuswestin/WebViewJavascriptBridge

JSBridge對象圖解:
這裏寫圖片描寫敘述

JSBridge實現完整流程:
這裏寫圖片描寫敘述

總結

那麼咱們在實際的開發中,怎樣針對Android和iOS的不一樣狀況,統一出一種完整的方案。


這裏寫圖片描寫敘述

另類實現:不採用url scheme方式

前面提到的JSBridge都是基於url scheme的,但事實上假設不考慮Android4.2下面,iOS7下面,事實上也可以用還有一套方案的。

  • Native調用JS的方法不變
  • JS調用Native是再也不經過觸發url scheme,而是採用自帶的交互
    詳細來說:
    Android中,原生經過 addJavascriptInterface開放一個統一的api給JS調用,而後將觸發url scheme步驟變爲調用這個api,其他步驟不變。

OS中,原生經過JavaScriptCore裏面的方法來註冊一個統一api,其他和Android中同樣。

相關文章
相關標籤/搜索