jsBridge笔记

介绍

原生APP跳转到H5页面时,往往需要携带一些用户信息,之前做法是在跳转的地址中拼接H5页面需要的参数,现在通过window.WebViewJavascriptBridge悄悄的进行数据交互。

  • 安卓环境:如果不存在window.WebViewJavascriptBridge变量,需要手动添加DomWebViewJavascriptBridgeReady事件监听;
  • ios环境:判断是否存在window.WebViewJavascriptBridge变量,如果不存在继续判断window.WVJBCallbacks变量,如果仍然不存在,则手动赋值为H5回调,然后document.createElement('iframe')插入document中,随即移除
  • 以上处理完成后,通过WebViewJavascriptBridge变量来注册【事件】以便APP能监听到并执行相应操作
  • 需要对安卓系统进行init处理,如果是安卓系统,注册事件之前需要先调用WebViewJavascriptBridge.init()
  • 注意: 一个页面整个生命周期过程中,只能进行一次init()否则会报错,我的做法是通过一个全局变量来判断是否初始化过

基本实现

// 针对安卓和ios系统,对window.WebViewJavascriptBridge进行兼容性处理 function setupWebViewJavascriptBridge(callback) { var u = navigator.userAgent var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1 var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge) } if (isAndroid) { document.addEventListener('WebViewJavascriptBridgeReady', function() { callback(WebViewJavascriptBridge) }, false); } if (isiOS) { if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function () { document.documentElement.removeChild(WVJBIframe) }, 0) } } setupWebViewJavascriptBridge(function(bridge) { var u = navigator.userAgent var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1 var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // 自己定义的全局变量,用来判断当前页面中安卓系统是否进行过一次初始化,页面卸载时取消赋值 if (!window.hasCalledWindowWebViewJavascriptBridge && isAndroid) { window.hasCalledWindowWebViewJavascriptBridge = true bridge.init(function(message, responseCallback) { console.log('JS got a message', message) responseCallback(message) }) } // js注册一个给原生APP捕获的方法 DemoToJSBridge。注册后,原生APP就可以直接访问 bridge.registerHandler('DemoToJSBridge', function(data, responseCallback) { console.log('native 传过来的是:', data) responseCallback(data) }) // JSToDemoBridge 已经在原生APP注册好了,js直接调用 bridge.callHandler('JSToDemoBridge', params, function(response) { console.log(response) }) })

示例

  • 前端定义一个DemoToJSBridge,供原生APP调用。
  • 原生APP定义一个JSToDemoBridge,供javascript调用
  • 传递的参数中格式:{ action: '', data: {/* ... */}},其中action用来标识操作,data是传递的数据
  • 前端在全局挂载一个$Demo,需要调用APP的原生方式时,直接$Demo.xxx()这么调用
  • 前端的jsBridge方法不能污染全局环境,可以使用自执行函数!(function() { /* ... */})()
  • 定义前端方法和action的对应关系
var protocol = { // 前端去调用原生APP的方法。比如:$Demo.getToken(),对应的action就是:get:token call: { setTopBarShare: "topBar:share", openShareBar: "shareBar:open", shareToHHmessage: "shareTo:hhmessage", shareToWXtimeline: "shareTo:wxtimeline", shareToWXmessage: "shareTo:wxmessage", shareToQQ: "shareTo:qq", shareToQzone: "shareTo:qzone", getToken: "get:token" }, // 原生APP主动去调用js的方法是,对应的响应。比如action为share:qzone,会执行前端的onShareQZone()方法 event: { onShareHHMessage: "share:hhmessage", onShareWxTimeline: "share:wxtimeline", onShareWxMessage: "share:wxmessage", onShareQQ: "share:qq", onShareQZone: "share:qzone" } }
  • 把对应的前端方法挂载到$Demo下面
!(function(w) { var protocol = { call: { setTopBarShare: "topBar:share", openShareBar: "shareBar:open", shareToHHmessage: "shareTo:hhmessage", shareToWXtimeline: "shareTo:wxtimeline", shareToWXmessage: "shareTo:wxmessage", shareToQQ: "shareTo:qq", shareToQzone: "shareTo:qzone", getToken: "get:token" }, event: { onShareHHMessage: "share:hhmessage", onShareWxTimeline: "share:wxtimeline", onShareWxMessage: "share:wxmessage", onShareQQ: "share:qq", onShareQZone: "share:qzone" } } var h = {} var eventMap = {} // 原生APP主动触发的方法 for (var key in protocol.event) { !(function() { var action = protocol.event[key] // $Demo.onShareQZone h[key] = function(cb) { eventMap[action] = function(data) { cb(data) } } })() } for (var key in protocol.call) { h[key] = function(cb) { console.log('not in app') } } w.$Demo = h })(this) // 这里用 this,在浏览器环境代表window,在node环境代表global
  • 编写setupWebViewJavascriptBridge
function setupWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } document.addEventListener('WebViewJavascriptBridgeReady', function() { callback(WebViewJavascriptBridge) }, false); window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0); }
  • 注册方法
function setupEvent(configData, callFun) { for (var key in protocol.call) { (function() { var action = protocol.call[key] h[key] = function(data, cb) { callFun(action, data, cb) } })() } configData.onShareHHMessage && h.onShareHHMessage(configData.onShareHHMessage); configData.onShareWxTimeline && h.onShareWxTimeline(configData.onShareWxTimeline); configData.onShareWxMessage && h.onShareWxMessage(configData.onShareWxMessage); configData.onShareQQ && h.onShareQQ(configData.onShareQQ); configData.onShareQZone && h.onShareQZone(configData.onShareQZone); } h.config = function(configData, configCb) { setupWebViewJavascriptBridge(function(bridge) { // js端注册方法,给APP调用 bridge.registerHandler('DemoToJSBridge', function(data, responseCallback) { var event = eventMap[data.action]; if(!event){ responseCallback({ 'result': 201 }); return; } event(data.data); responseCallback({ 'result': 0 }); }) // js调用APP方法 setupEvent(configData, function(action, data, cb) { bridge.callHandler('JSToDemoBridge', { action: action, data: data }, function(response) { cb(response) }) }) configCb() }) }
  • 完整代码如下
!(function(w) { var protocol = { call: { setTopBarShare: "topBar:share", openShareBar: "shareBar:open", shareToHHmessage: "shareTo:hhmessage", shareToWXtimeline: "shareTo:wxtimeline", shareToWXmessage: "shareTo:wxmessage", shareToQQ: "shareTo:qq", shareToQzone: "shareTo:qzone", getToken: "get:token" }, event: { onShareHHMessage: "share:hhmessage", onShareWxTimeline: "share:wxtimeline", onShareWxMessage: "share:wxmessage", onShareQQ: "share:qq", onShareQZone: "share:qzone" } }; var h = {} var eventMap = {} for (var key in protocol.event) { (function() { var action = protocol.event[key] h[key] = function(cb) { eventMap[action] = function(data) { cb(data) } } })() } for (var key in protocol.call) { h[key] = function(cb) { console.log('not in app') } } function setupEvent(configData, callFun) { for (var key in protocol.call) { (function() { var action = protocol.call[key] h[key] = function(data, cb) { callFun(action, data, cb) } })() } configData.onShareHHMessage && h.onShareHHMessage(configData.onShareHHMessage); configData.onShareWxTimeline && h.onShareWxTimeline(configData.onShareWxTimeline); configData.onShareWxMessage && h.onShareWxMessage(configData.onShareWxMessage); configData.onShareQQ && h.onShareQQ(configData.onShareQQ); configData.onShareQZone && h.onShareQZone(configData.onShareQZone); } function setupWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } document.addEventListener('WebViewJavascriptBridgeReady', function() { callback(WebViewJavascriptBridge) }, false); window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0); } w.$Demo = h h.config = function(configData, configCb) { setupWebViewJavascriptBridge(function(bridge) { // js端注册方法,给APP调用 bridge.registerHandler('DemoToJSBridge', function(data, responseCallback) { var event = eventMap[data.action]; if(!event){ responseCallback({ 'result':201}); return; } event(data.data); responseCallback({ 'result':0}); }) // js调用APP方法 setupEvent(configData, function(action, data, cb) { bridge.callHandler('JSToDemoBridge', { action: action, data: data }, function(response) { cb(response) }) }) configCb() }) } })(this)
  • 使用示例
window.$Demo.config({}, () => { window.$Demo.getToken({ key: userKey }, (e) => { console.log(e) }) }) })

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: