JSBridge 原理以及各种方式的优缺点

在开发 web App 的时候,我们会用 JSBridge 来调用 Native 的一些功能,作为一名前端开发,JSBridge 实现这块不一定是我们开发的,但也要对 JSBridge 实现原理做到了解。

JS 调用 Native

1. 拦截 Url Schema(假请求)

2. 拦截 prompt alert confirm

3. 注入 JS 上下文

  • 拦截 Url Schema(假请求)

h5 和 native 约定一个通信协议,例如 jsbridge, 同时约定调用 native 的方法名作为域名,以及带上参数 和 接收方法执行结果的 js 方法名 cbName,例如

"jsbridge://openScan?{"data": {"scanType": "qrCode"}, "cbName": "handleScanResult"}"

优点:

因为它 支持 iOS6,所以为了实现兼容很多方案会使用这种方式。

缺点:

  1. 连续发送时消息丢失
window.location.href = "jsbridge://callNativeNslog?{"data": "111", "cbName": ""}";
window.location.href = "jsbridge://callNativeNslog?{"data": "222", "cbName": ""}";

js 此时的诉求是在同一个运行逻辑内,快速的连续发送出 2 个通信请求,用客户端本身 IDE 的 log,按顺序打印 111,222,那么实际结果是 222 的通信消息根本收不到,直接会被系统抛弃丢掉。

原因:因为 h5 的请求归根结底是一种模拟跳转,跳转这件事情上 webview 会有限制,当 h5 连续发送多条跳转的时候,webview 会直接过滤掉后发的跳转请求,因此第二个消息根本收不到。
2. URL 长度限制
如果需要传输的数据较长,例如方法参数很多时,由于 URL 长度限制,会丢失部分数据。
3. 耗时长
创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长。
4. web 环境报错
在浏览器环境下,会跳转到不存在的页面

  • 拦截 prompt alert confirm

因为 alert confirm 比较常用,所以一般通过 prompt 进行通信。

约定的传输数据的组合方式以及 js 端封装方法的可以类似上面的 拦截 URL Schema 提到的方式。

function callNative(methodName, arg, cb) {
    ...

    const url = 'jsbridge://' + method + '?' + JSON.stringify(args);

    prompt(url);
}

native 会拦截 h5 发出的 prompt,当检测到协议为 jsbridge 而非普通的 http/https/file 等协议时,会拦截该请求,解析出 URL 中的 methodName、arg、 cbName,执行该方法并调用 js 回调函数。

优点:

没发现

缺点:

  1. iOS 的 UIWebView 不支持该方式(WKWebView 支持)
  2. 浏览器中,如果有地方需要用到 prompt 就会有副作用
  • 注入 JS 上下文

即由 native 将实例对象通过 webview 提供的方法注入到 js 全局上下文,js 可以通过调用 native 的实例方法来进行通信。

具体有安卓 webview 的 addJavascriptInterface,iOS UIWebview(iOS2+) 的 JSContext,iOS WKWebview(iOS8+)的 scriptMessageHandler。

h5 端可以在 js 调用 window._jsbridge 实例下面的 call 方法,传入的数据组合方式可以类似上面两种方式。具体代码如下:

window.callbackId = 0;

function callNative(method, arg, cb) {
  let args = {
    data: arg === undefined ? null : JSON.stringify(arg)
  };

  if (typeof cb === 'function') {
    const cbName = 'CALLBACK' + window.callbackId++;
    window[cbName] = cb;
    args['cbName'] = cbName;
  }

  if (window._jsbridge) {
    window._jsbridge.call(method, JSON.stringify(args));
  }
}

优点:

官方提供,方便简捷

缺点:

  1. Native 注入的方法和时机都受限,JS 调用 Native 之前需要先判断 JSBridge 是否注入成功

  2. 在安卓 4.2 以下有安全漏洞,在 4.2 之前,Android 注入 JavaScript 对象的接口是 addJavascriptInterface,但是这个接口有漏洞,可以被不法分子利用,危害用户的安全,因此在 4.2 中引入新的接口 @JavascriptInterface(上面代码中使用的)来替代这个接口,解决安全问题。

Native 调用 JS

1. loadUrl

2. evaluateJavascript

  • loadUrl

Native 调用 JS 比较简单,只要 H5 将 JS 方法暴露在 Window 上给 Native 调用即可。

mWebview.loadUrl("javascript: func()");
  • evaluateJavascript

mWebview.evaluateJavascript("javascript: func()", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        return;
    }
});
方式 优点 缺点
loadUrl 兼容性好 1. 会刷新页面 2. 无法获取 js 方法执行结果
evaluateJavascript 1. 性能好 2. 可获取 js 执行后的返回值 仅在安卓 4.4 以上可用

参考:
https://segmentfault.com/a/1190000025182935
https://segmentfault.com/a/1190000021818496
https://juejin.cn/post/6844903936248250375#heading-2