JSBridge深度剖析

來源:互聯網
上載者:User

標籤:UI   第三方   ring   res   line   .net   webview控制項   不同   傳遞參數   

概述

做過混合開發的人都知道Ionic和PhoneGap之類的架構,這些架構在web基礎上封裝一層Native,然後通過Bridge技術的js調用本地的庫。

在講JSBridge技術之前,我們來看一下傳統的實現方式。

Android端Native調JS

native調用js比較簡單,只要遵循:”javascript: 方法名(‘參數,需要轉為字串’)”的規則即可。

在4.4之前,調用的方式:

// 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),使用以下方式:

mWebView.evaluateJavascript("javascript: 方法名(‘參數,需要轉為字串‘)", new ValueCallback() {        @Override        public void onReceiveValue(String value) {            //這裡的value即為對應JS方法的傳回值        }});

說明:

  • 4.4之前Native通過loadUrl來調用JS方法,只能讓某個JS方法執行,但是無法擷取該方法的傳回值
  • 4.4之後,通過evaluateJavascript非同步呼叫JS方法,並且能在onReceiveValue中拿到傳回值
  • 不適合傳輸大量資料(大量資料建議用介面方式擷取)
  • mWebView.loadUrl(“javascript: 方法名(‘參數,需要轉為字串’)”);函數需在UI線程運行,因為mWebView為UI控制項
JS調Native

Js調用Native需要對WebView設定@JavascriptInterface註解,這裡有個漏洞,後面會給大家說明。要想js能夠Native,需要對WebView設定以下屬性。

WebSettings webSettings = mWebView.getSettings();   //Android容器允許JS指令碼webSettings.setJavaScriptEnabled(true);//Android容器設定僑連對象mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

這裡我們看到了getJSBridge(),Native中通過addJavascriptInterface添加暴露出來的JS橋對象,然後再該對象內部聲明對應的API方法。

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的方法呢?

//調用方法一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的寫法。

//Swiftwebview.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);//觸發schememessagingIframe.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中一樣。

JSBridge深度剖析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.