JSBridge 實現原理

本位主要總結下 JSBridge 前端實現原理,來自工做中的總結,安卓/ios代碼僅爲示意

JavaScript 與 Native之間的互相調用

JavaScript是運行在一個單獨的 JS Context中(例如: webview的webkit引擎,JSCore)
1. 注入api的形式
  • 安卓操做方法
// 安卓4.4版本以前,沒法獲取返回值
    // mWebView = new WebView(this); // 即當前webview對象
    mWebView.loadUrl("javascript: 方法名('參數,須要轉爲字符串')")

    // 安卓4.4及之後
    mWebView.evaluateJavascript("javascript: 方法名,參數須要轉換爲字符串", new ValueCallback() {
     @Override
     public void onReceiveValue(String value) {
       // 這裏的value即爲對應JS方法的返回值
     }
    })

  // 總結:
    1. 4.4 以前Native經過loadUrl來調用js方法,只能讓某個js方法執行,可是沒法獲取該方法的返回值
    2. 4.4 以後,經過evaluateJavaScript異步調用js方法,而且能在onReceive中拿到返回值
    3. 不適合傳輸大量數據
    4. mWebView.loadUrl("javascript: 方法名") 函數需在UI線程運行,由於mWebView爲UI控件,會阻塞UI線程
    
    // JS調用Native
    // 安卓環境配置
    WebSettings webSettings = mWebView.getSettings();
    // Android容器容許js腳本,必需要
    webSettings.setJavaScriptEnabled(true);
    // Android 容器設置僑連對象
    mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");
  
    // Android中JSBridge的業務代碼
    private Object getJSBridge() {
      Object insterObj = new Object() {
        @JavascriptInterface
        public String foo() {
          // 此處執行 foo  bridge的業務代碼
          return "foo" // 返回值
        }
  
        @JavascriptInterface
        public String foo2(final String param) {
          // 此處執行 foo2 方法  bridge的業務代碼
          return "foo2" + param;
        }
      }
      return inserObj;
  
    }
  
  
    // html 中 js調用原生的代碼
    // JSBridge 經過addJavascriptInterface已被注入到 window 對象上了
    window.JSBridge.foo(); // 返回 'foo'
  
    window.JSBridge.foo2(); // 返回 'foo2:test'
  
    注意:在安卓4.2以前 addJavascriptInterface有風險,hacker能夠經過反編譯獲取Native註冊的Js對象,而後在頁面經過反射Java的內置 靜態類,獲取一些敏感的信息和破壞
  • ios 操做方法
// native 調用 js

  // UIWebview
  [webView stringByEvaluatingJavaScriptFromString:@"方法名(參數);"];
    
    // WKWebview
    [_customWebView evaluateJavaScript:[@"方法名(參數)"] completionHandler:nil];
    

    --------------------
      
  // js 調用 native
    
    // 引用官方庫文件 UIWebview(ios8 之前的版本,建議棄用)
    #import <JavaScriptCore/JavaScriptCore.h>
    // webview 加載完畢後設置一些js接口
    -(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];
        };
  }
    
    // js 調用原生代碼
  window.foo('test'); // 返回 'foo:test'
    
  // 注意:ios7 之前 js沒法調用native方法,ios7以後能夠引入第三方提供的 JavaScriptCore 庫
    總結:
    1. ios7 纔出現這種方式,在這以前js沒法直接調用Native,只能經過JSBridge方式調用
    2. JS 能調用到已經暴露的api,而且能獲得相應返回值
    3. ios原生自己是沒法被js調用的,可是經過引入官方提供的第三方「JavaScriptCore」,便可開發api給JS調用
      
    
    // WKWebview  ios8以後纔出現,js調用native方法
    // ios 代碼配置 https://zhuanlan.zhihu.com/p/32899522
    // js代碼
      window.webkit.messageHandlers.JSBridge.postMessage(msgObj);
    
    
    ios開發自帶兩種webview控件
      UIWebview(ios8 之前的版本,建議棄用)
      版本較老
      可以使用JavaScriptCore來注入全局自定義對象
      佔用內存大,加載速度慢
    WKWebview
      版本較新
      加載速度快,佔用內存小
      js使用全局對象window.webkit.messageHandlers.{NAME}.postMessage 來調用native的方法

原生和h5 的另外一種通信方式:最廣爲流行的方法 JSBridge-橋協議

JSBridge 是廣爲流行的Hybrid 開發中JS和Native一種通訊方式,簡單的說,JSBridge就是定義Native和JS的通訊,Native只經過一個固定的橋對象調用JS,JS也只經過固定的橋對象調用native,

基本原理是:javascript

h5 --> 經過某種方式觸發一個url --> native捕獲到url,進行分析 -->原生作處理 --> native 調用h5的JSBridge對象傳遞迴調html

爲何要用JSBridge

上面咱們看到native已經和js實現通訊,爲何還要經過url scheme 的這種jsBridge方法呢前端

  1. Android4.2 一下,addJavaScriptInterface方式有安全漏洞
  2. ios7如下,js沒法調用native
  3. url scheme交互方式是一套現有的成熟方案,能夠兼容各類版本
  • 注意:jsBridge是一種交互理念一種協議,而上述url scheme則是其中的一種實現方式,因此也就是說,就算後面實現變爲了 addJavaScriptInterface、JavaScriptCore,也是同樣和JSBridge交互
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,根據定好的協議(scheme://method...),分析當前觸發了哪一種方法,而後根據定義來實現java

實現一個JSBridge
1. 設計出一個native與js交互的`全局橋對象`
 2. js如何調用native
 3. native如何得知api被調用
 4. 分析 url 參數和回調的格式
 5. native如何調用js
 6. h5中api方法的註冊以及格式
  • 設計一個native與js交互的全局對象 ==> 規定js和native之間的通訊必須經過一個h5全局對象JSBridge來實現
// 名稱: JSBridge 掛在 window上的一個屬性
  var JSBridge = window.JSBridge || (window.JSBridge = {});
  /**
    該對象有以下方法:
    registerHandler(String, Function) 註冊本地 js 方法,註冊後 native可經過 JSBridge調用,註冊後會將方法註冊到本地變量 messageHandles中
    
    sendHandler(String, JSON, Function) h5 調用原生開放的api,調用後實際上仍是本地經過 url scheme觸發,調用時會將回調 id 存放到本地變量responseCallbacks 中
    
    _handleMessageFromNative h5 調用native以後的回調通知
    參數爲 {reposeId: 回調id, responseData: 回調數據}
    
  */
  
  var JSBridge = {
    // 註冊本地方法供原生調用
    registerHandler: function(method, cb) {
      // 會將cb 放入 messageHandlers裏面,待原生調用
    },
    messageHandles: {}, // h5註冊方法集合,供native通知後回調調用
    
    // h5 主動調用native,需生成惟一的callbackId
    sendHandler: function(mathod, data, succCb, errCb) {
      // 內部經過iframe src url scheme 向native發送請求
      // 並將對應的回調註冊進 responseCallbacks
      // native 處理結束後將結果信息通知到h5 經過 _handleMessageFromNative
      // h5 拿到返回信息處理 responseCallbacks 裏對應的回調
    },
    responseCallbacks: {}, // 回調集合
    
    // native 通知 h5
    _handleMessageFromNative: function(message) {
       // 解析 message,而後根據通知類型執行 messageHandles 或 responseCallbacks裏的回調
    }
  }
  
  /**
      注意:
      1. native 調用_handleMessageFromNative通知h5,參數爲 json 字符串
      
      2. native 主動調用h5方法時 {methodName: api名, data, callbackId}
          methodName: 開放api的名稱
          data: 原生處理後傳遞給 h5 參數
        須要把回調函數的值 return 出去,供native拿到,
        
        或者再發一個 bridge 回去,方法名是 methodNameSuccess,或者嚴禁掉,方法名爲native生產的callbackId
  */
  如:
  bridge.register("hupu.ui.datatabupdate", (name) => {
    if(name) {
      // 再發一個bridge通知原生tab更新成功,,,method 能夠爲native生成的 callbackId
      bridge.send('hupu.ui.datatabsuccess', {}) 
    }
  });
  • js 如何調用native ==> 經過 sendHandler 方法調用原生
// sendHandler 執行步驟
1. 判斷是否有回調函數,若是有,生成一個回調函數id,並將id,和對應的回調添加放入回調函數集合 responseCallbacks 中

2. 經過特定的參數轉換方法,將傳入的數據,方法名一塊兒拼接成一個 url scheme,以下:
 var param = {
   method: 'methodName',
   data: {xx: 'xx'},
   success: 'successId',
   error: 'errorId'
 }
 // 變成字符串並編碼
 var url = scheme://ecape(JSON.stringify(param))

3. 使用內部建立好的iframe來觸發scheme(location.href = 可能會形成跳轉問題)
 ...建立iframe
 var iframe = document.createElment('iframe');
 iframe.src = url;
 document.head.appendChild(iframe);
 setTimeout(() => document.head.removeChild('iframe'), 200)
  • native 如何得知 api 被調用ios

    • 安卓捕獲 url scheme:shouldoverrideurlloading 捕獲到url進行分析
    • 安卓端也可經過h5的 window.prompt(url, '') 來觸發scheme,而後native經過重寫webviewClientonJsPrompt 來獲取url,而後解析
    • ios: 在 UIWebView WKWebview 內發起的全部網絡請求,均可以經過 delegate函數在native層獲得通知,經過 shouldStartLoadWithRequest捕獲webview中觸發的url scheme
  • 分析url參數和回調的格式git

    • native已經接收到了js調用的方法,接下來原生應該按照定義好的數據格式來解析數據了,從url中提取出 method、data、successId、errorId
    • 根據方法名,再本地尋找對應的方法,接收對應的參數進行執行,執行完畢後,而後通知 h5並攜帶相應參數
  • native如何調用 js (參照上面的native執行js的方法)github

    • h5調用native後的被動通知 JSBridge._handleMessageFromNative(messageJSON),json格式:{responseId, reponseData}
    • native 主動調用h5 註冊的方法,JSBridge._handleMessageFromNative(param),param 格式爲 {methodName, data},因爲是異步不支持批量調用

參考資料

  1. https://www.cnblogs.com/dailc...
  2. https://zhuanlan.zhihu.com/p/...JSBridgeDemo
  3. https://github.com/chemdemo/c...
相關文章
相關標籤/搜索