JSBridge 框架解析

非常需要注意的是關於安卓端 在assets文件夾裏注入的js橋樑文件 一定不要有註釋 或者是壓縮成一行代碼才能注入成功

// notation: js file can only use this kind of comments
// since comments will cause error when use in webview.loadurl,
// comments will be remove by java use regexp
// version 0.1.1
// write by shuchong
(function () {
  if (window.la) {
    return
  }
  // native和js的溝通,通過發送消息、接收隊列、處理消息的邏輯來處理
  // js發給native用url
  // native發給js通用調用_handleMessageFromNative方法
  // 發送消息請求url的iframe
  var messagingIframe
  // js發送消息的隊列
  var sendMessageQueue = []
  // js接收消息的隊列
  var receiveMessageQueue = []
  // 接收消息處理的方法集
  var messageHandlers = {}

  var CUSTOM_PROTOCOL_SCHEME = 'yy'
  var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'

  // 發送信息給安卓後,安卓回調js的callback方法集
  var responseCallbacks = {}
  // 安卓回調js的callback方法集的id
  var uniqueId = 1

  // 創建發送消息的iframe
  function _createQueueReadyIframe (doc) {
    messagingIframe = doc.createElement('iframe')
    messagingIframe.style.display = 'none'
    doc.documentElement.appendChild(messagingIframe)
  }

  // set default messageHandler  初始化默認的接收消息隊列
  // messageHandler爲默認的js端收到消息的處理函數
  function init (messageHandler) {
    if (la._messageHandler) {
      throw new Error('WebViewJavascriptBridge.init called twice')
    }
    la._messageHandler = messageHandler
    var receivedMessages = receiveMessageQueue
    receiveMessageQueue = null
    for (var i = 0; i < receivedMessages.length; i++) {
      _dispatchMessageFromNative(receivedMessages[i])
    }
    console.log('la inited');
  }

  // 發送
  function send (data, responseCallback) {
    _doSend({
      data: data
    }, responseCallback)
  }

  // 註冊線程 往數組裏面添加值
  function registerHandler (handlerName, handler) {
    messageHandlers[handlerName] = handler
  }
  // JS調用Native方法時,通過該方法出發native的shouldOverrideUrlLoading方法,使Native主動向JS取數據
  // 調用線程
  // js調用native方法
  function callHandler (handlerName, data, responseCallback) {
    _doSend({
      handlerName: handlerName,
      data: data
    }, responseCallback)
  }

  // 3、JS將數據發送到Native端
  // sendMessage add message, 觸發native的 shouldOverrideUrlLoading方法,使Native主動向JS取數據
  // *******************
  // 把消息隊列數據放到shouldOverrideUrlLoading 的URL中不就可以了嗎??
  // 爲什麼還要Native主動取一次,然後再放到shouldOverrideUrlLoading的URL中返回??
  function _doSend (message, responseCallback) {
    // 發送的數據存在
    if (responseCallback) {
      //
      var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime()
      responseCallbacks[callbackId] = responseCallback
      message.callbackId = callbackId
    }
    // 添加到消息隊列中
    sendMessageQueue.push(message)
    // 讓Native加載一個新的頁面
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
  }

  // 將數據返回給Native
  // 提供給native調用,該函數作用:獲取sendMessageQueue返回給native,由於android不能直接獲取返回的內容,
  // 所以使用url shouldOverrideUrlLoading 的方式返回內容
  function _fetchQueue () {
    // json數據
    var messageQueueString = JSON.stringify(sendMessageQueue)
    // message數據清空
    sendMessageQueue = []
    // 數據返回到shouldOverrideUrlLoading
    // android can't read directly the return data, so we can reload iframe src to communicate with java
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString)
  }

  // 2、分發Native的消息
  function _dispatchMessageFromNative (messageJSON) {
    setTimeout(function () {
      // 解析消息
      // todo:try catch
      var message = JSON.parse(messageJSON)
      //
      var responseCallback
      // java call finished, now need to call js callback function
      if (message.responseId) {
        responseCallback = responseCallbacks[message.responseId]
        if (!responseCallback) {
          return
        }
        responseCallback(message.responseData)
        delete responseCallbacks[message.responseId]
      } else {
        // 消息中有callbackId 說明需要將處理完成後,需要回調Native端
        // 直接發送
        if (message.callbackId) {
          // 回調消息的 回調ID
          var callbackResponseId = message.callbackId
          //
          responseCallback = function (responseData) {
            // 發送JS端的responseData
            _doSend({
              responseId: callbackResponseId,
              responseData: responseData
            })
          }
        }
        // jsBridge的js端默認回調
        var handler = la._messageHandler
        if (message.handlerName) {
          handler = messageHandlers[message.handlerName]
        }
        // 查找指定handler
        try {
          handler(message.data, responseCallback)
        } catch (exception) {
          if (typeof console !== 'undefined') {
            console.log('WebViewJavascriptBridge: WARNING: javascript handler threw.', message, exception)
          }
        }
      }
    })
  }
  // 1.收到Native的消息
  function _handleMessageFromNative (messageJSON) {
    //
    console.log(messageJSON)
    // 添加到接收消息隊列
    if (receiveMessageQueue) {
      receiveMessageQueue.push(messageJSON)
    }
    // 分發Native消息
    _dispatchMessageFromNative(messageJSON)
  }

  function listen (eventName, handler) {
    callHandler(eventName)
    messageHandlers[eventName] = handler
  }
  //   mossSpeech
  // Array.<string>
  // 註冊監聽的語音識別文字。用戶說該文字觸發回調函數執行。
  // mossSkillset
  // Array.<string>
  // 註冊監聽的語義skillcommand。用戶語義生成skillcommand如果命中設置的內容,則觸發回調。
  //   wx.addMossEventListener({
  //     mossSpeech:['刷新支付二維碼'],
  //     mossSkillset:['Search']
  // Tips:  當前後臺語意註冊需要兩部分字段:intent/domain;
  // // 爲保持參數一致,故Skillset形式約定爲 ‘intent[]domain’,如:
  // // mossSkillset:['miniProgram[]search', 'generalControl[]nextPage']
  //   }, onSkillCommand)

  // words 爲字符串數組, 最少有一個詞
  function registerSpeech (words, handler) {
    callHandler('_registerSpeech', words)
    words.forEach(word => {
      messageHandlers['_speech' + word] = handler
    })
  }

  // init(messageHandler),初始化,設置js收到消息時的默認messageHandler,並消化所有init之前已接收的消息
  // send(data, responseCallback),js端發送消息給native
  // registerHandler,js端註冊某native消息的處理方法,消息的handlerName,registerHandler(handlerName, handler)
  // callHandler,js端調用native方法,callHandler(handlerName, data, responseCallback)
  // listen, js端註冊異步響應的消息處理方法listen(eventName, data, handler,isAdd=true),isAdd是否是增加的處理,false時清空之前的處理handler
  // 這個功能還沒想好-------unListen(eventName,data), 取消監聽(安卓要做出相應處理,比如語音免喚醒詞的反註冊)
  // _fetchQueue,native調用,獲取消息data的方法
  // _handleMessageFromNative,native調用,發送消息給js端
  var la = window.la = {
    listen: listen,
    registerHandler: registerHandler,
    registerSpeech: registerSpeech,
    _fetchQueue: _fetchQueue,
    _handleMessageFromNative: _handleMessageFromNative
  };

  ['showTitle', 'playTTS','navigateMap'].forEach(a => {
    la[a] = function (data, responseCallback) {
      callHandler(a, data, responseCallback)
    }
  })

  var doc = document
  _createQueueReadyIframe(doc)
  var readyEvent = doc.createEvent('Events')
  readyEvent.initEvent('WebViewJavascriptBridgeReady')
  readyEvent.bridge = la
  doc.dispatchEvent(readyEvent)
  init()
})()

壓縮後的js文件

!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n,t){e.exports=t(1)},function(e,n){!function(){if(!window.la){var e,n=[],t=[],r={},o={},a=1,i=window.la={listen:function(e,n){u(e),r[e]=n},registerHandler:function(e,n){r[e]=n},registerSpeech:function(e,n){u("_registerSpeech",e),e.forEach(e=>{r["_speech"+e]=n})},_fetchQueue:function(){var t=JSON.stringify(n);n=[],e.src="yy://return/_fetchQueue/"+encodeURIComponent(t)},_handleMessageFromNative:function(e){console.log(e),t&&t.push(e),f(e)}};["showTitle","playTTS","navigateMap","login"].forEach(e=>{i[e]=function(n,t){u(e,n,t)}});var c=document;!function(n){(e=n.createElement("iframe")).style.display="none",n.documentElement.appendChild(e)}(c);var l=c.createEvent("Events");l.initEvent("WebViewJavascriptBridgeReady"),l.bridge=i,c.dispatchEvent(l),function(e){if(i._messageHandler)throw new Error("WebViewJavascriptBridge.init called twice");i._messageHandler=e;var n=t;t=null;for(var r=0;r<n.length;r++)f(n[r]);console.log("la 1.0.0 inited")}()}function u(e,n,t){s({handlerName:e,data:n},t)}function s(t,r){if(r){var i="cb_"+a+++"_"+(new Date).getTime();o[i]=r,t.callbackId=i}n.push(t),e.src="yy://__QUEUE_MESSAGE__/"}function f(e){setTimeout((function(){var n,t=JSON.parse(e);if(t.responseId){if(!(n=o[t.responseId]))return;n(t.responseData),delete o[t.responseId]}else{if(t.callbackId){var a=t.callbackId;n=function(e){s({responseId:a,responseData:e})}}var c=i._messageHandler;t.handlerName&&(c=r[t.handlerName]);try{c(t.data,n)}catch(e){"undefined"!=typeof console&&console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.",t,e)}}}))}}()}]);

然後我們進入正題來詳細說明一下jsbridge的用法
首先是安卓端





JS端

首先註冊安卓端注入的js文件的對象

document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
console.log('la is ready')
},
false
);

然後就是約定的相關方法調用 js調用安卓本地註冊的方法 傳遞網頁的數據給到安卓端使用
下面是獲取經緯度座標來傳遞給安卓端 安卓端去調用導航來實現功能的js端代碼

la.navigateMap({
latitude: detailData.lat +'', // gcj02座標
longitude: detailData.lon +'', // gcj02座標
address: detailData.address +'' // 地址 // 地址
},function (A2JData) {
console.log("form Android to JS:" + A2JData);
})

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章