原生App与javascript交互

jsBridge在支付,钱包,媒体拓展,图片处理,活动页面,用户地理位置网络状态都能得到原生强有力支持;

前端和Native对对方的细节知道的越少越好,减少耦合度,暴露的接口尽量控制在5个以内;

实现方式(交互形式)

Native 调用 JS

使用前端暴露在window下的一个方法或者一个对象的方法;

window._handlerFromApp(message)
window.JSBridge._handlerFromApp(message)
message: {
  cbId  : "cb_(:id)_(:timeStamp)",      //回调函数的id
  status: 0,                     //状态数据 (0:失败, 1:成功)
  msg   : "ok",                         //反馈的消息
  data  : {
    //...                               //一些处理后的数据
  } 
}

JS调用Native

以下只介绍前两个方法,第三个和第二个比较类似

  • A. Native暴露一个含有通信方法的类给web调用
  • B. Native拦截iframe请求
  • C. Native拦截prompt弹出框

A 一个包含调用方法的类

iOS : 可使用javascriptCore
Android: 直接使用WebView的addJavascriptInterface方法

将一个js对象绑定到一个Native类,在类中实现相应的函数,当js需要调用Native的方法时,只需要直接在js中通过绑定的对象调用相应的函数

确定对象名称: (:AppName)JSBridge

Native提供的对象含有的方法:

  • invoke(funcName, data)
  • listen(funcName, data)

invoke:用于web页面调用Native私有方法的通用方法

参数funcName, data
funcName:对应为Native内部私有方法的方法名或映射
data :web传递给Native的必要数据

// data 数据
{
  cbId : "cb_(:id)_(:timeStamp)",  //回调函数的id
  msg  : {}                        //提供给使用方法执行的一些参数
}
/** 
  //1.拿wx参考为例
  wx.previewImg({
    current: 'http://xxx_1.png',
    urls   : [
      'http: //xxx_0.png',
      'http: //xxx_1.png',
      'http: //xxx_2.png',
      'http: //xxx_3.png',
    ]
  });
  //2.因为wx对jsbridge进行了一次封装,jssdk, 而我们在未封装时应该如下使用
  JSBridge.invoke('imagePreview', {
    cbId : "cb_(:id)_(:timeStamp)",
    msg : {
      current: 'http://xxx_1.png',
      urls   : [
        'http: //xxx_0.png',
        'http: //xxx_1.png',
        'http: //xxx_2.png',
        'http: //xxx_3.png',
      ]
    }
  });

那么当调用之后,Native执行完成对应的私有方法后,执行一次我们提供的回调接口,以下是javascript的语法,请Native开发工程师对应修改

JSBridge.handlerFromApp({
  cbId  : "cb_(:id)_(:timeStamp)", //web传给Native的cbId
  status: 1,                       //状态数据 (0:失败, 1:成功)
  msg   : "预览成功", 
  data  : {} 
});

listen是一个用于web页面监听Native方法实现的通用方法

使用环境: 不属于web页面上的操作。当用户直接操作Native上的功能来影响或发送数据给web,或者操作的功能需要用到web页面上的数据,我们需要告知Native我们希望能收到回调;

例子: 微信监听分享操作

  1. 分享的内容是web上的内容(标题,描述,图片);
  2. 获取分享操作是否完成和分享操作的数据收集;
  3. 分享按钮是原生APP提供;

数据结构和操作与invoke相似,对应Native开发哥们接收到listen操作后需要存储一个映射,在被监听的操作实现上判断是不是需要执行web端提供的回调接口;

B iframe的魔法

由于Native App可以监听webview的请求,所以web端通过创建一个隐藏的iframe,请求商定后的统一协议来发送数据给Native App;

需要Native开发兄弟在webview开启时候为页面注入jsbridge.js代码并执行(防止被前端浏览器直接查看源代码了解app的代码逻辑)

页面前期准备

1.app打开webview
2.loadUrl(页面url)
3.监听webview开始,并执行一段js代码将包内的jsbridge.js文件引入页面中;

比较

A:android曝安全漏洞,但相对来说实现简单,调用方式容易,且传递参数,无需前端搭建jsbridge,只需要封装易用的sdk,App不需要读取本地静态js文件;

B: iframe规定协议,规范统一,需要前端实现jsbridge和封装sdk, iframe通过url的方式,数据统一为字符串格式,数据量受限制,两端要转义字符;

C: prompt在一些安卓设备受系统劫持,监听prompt兼容性需要测试,也是字符串形式,数据量不受限,需要转义字符;

/**
 * 用法:
 * import jsBridge from 'fileName.js'
 * 
 * 1、给 APP 端发送数据
 * jsBridge.callHandler(eventName, data, callback(reponseData))
 * 参数说明:
 * eventName (string): 必传, 与 APP 端约定的事件名
 * data (object): 非必传, 发送给 APP 端的数据
 * callback (function): 通信完成后,前端的回调,reponseData,是APP端返回的数据
 * 
 * 2、接收 APP 端的数据
 * jsBridge.registerHandler(eventName, callback(data, responseCallback))
 * 参数说明:
 * eventName (string): 必传,与 APP 端约定的事件名
 * callback (function): data: 是接收到的数据,responseCallback,通信完成后,传给 APP 端的回调
 */
 
const isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Adr') > -1;
const isiOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
 
//这是必须要写的,用来创建一些设置
function setupWebViewJavascriptBridge(callback) {
  if (isAndroid) {
    if (window.WebViewJavascriptBridge) {
      callback(WebViewJavascriptBridge)
    } else {
      document.addEventListener(
        'WebViewJavascriptBridgeReady',
        function () {
          callback(WebViewJavascriptBridge)
        },
        false
      );
    }
  }
  if (isiOS) {
    if (window.WebViewJavascriptBridge) {
      return callback(WebViewJavascriptBridge);
    }
    if (window.WVJBCallbacks) {
      return window.WVJBCallbacks.push(callback);
    }
    window.WVJBCallbacks = [callback];
    let WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function () {
      document.documentElement.removeChild(WVJBIframe)
    }, 0);
  }
}
setupWebViewJavascriptBridge(function (bridge) {
  if (isAndroid) {
    //安卓端,接收数据时,需要先进行初始化
    bridge.init(function (message, responseCallback) {
      const data = {
        'Javascript Responds': 'Wee!'
      };
      responseCallback(data);
    })
  }
})
 
export default {
  // 给APP发送数据
  callHandler(name, data, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      bridge.callHandler(name, data, callback)
    })
  },
  // 接收APP端的数据
  registerHandler(name, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      bridge.registerHandler(name, function (data, responseCallback) {
        callback(data, responseCallback)
      })
    })
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *