JSBridge 基础知识

180 阅读10分钟

JSBridge

JSBridge 是 WebView 与原生应用(Native)之间的双向通信机制:JavaScript 可以调用原生功能,原生也可以调用 Web 端的 JavaScript 方法。

本质要解决的问题:Web 跑在沙箱里,不能直接碰相机、定位、打印等系统能力;Native 也不能直接操作页面 DOM。两端需要一条约定好的「管道」。

知识地图

概念层    JSBridge = 通信架构 / 协议(不是某一段固定代码)
              │
实现层    URL 拦截  ·  代码注入  ·  MessageHandlers(WKWebView 通道)
              │
库 / API  JsBridge、DSBridge、微信 JS-SDK …
              │
调用层    callHandler / registerHandler  或  约定 URL + callbackId

建议阅读顺序:先分清「概念 ≠ 某个全局对象」→ 再看消息协议 → 再对比两种实现 → 最后看前端怎么写。


一、先分清三层:概念、库、入口对象

window.WebViewJavascriptBridge 不是 JSBridge 本身,只是开源库 WebViewJavascriptBridge 挂到页面上的调用入口。

名称层级含义
JSBridge抽象概念 / 协议Web(JS)与 Native(OC / Java 等)双向通信的架构。URL Scheme 拦截、注入 API、MessageChannel 等能打通两端隔离的方案,都属于 JSBridge
WebViewJavascriptBridge具体开源库封装 iOS / Android WebView 差异,注入脚本,暴露 callHandler、registerHandler
window.WebViewJavascriptBridge调用入口页面里实际调的那个全局对象

可类比为:

JSBridge ≈ HTTP;WebViewJavascriptBridge ≈ Axios;callHandler ≈ axios.post()。

其他实现的通信原理相同,只是全局对象和方法名不同:

实现全局对象 / 典型 API
WebViewJavascriptBridgewindow.WebViewJavascriptBridge.callHandler()
微信 JS-SDKwindow.wx,如 wx.invoke()、wx.ready()
支付宝 JS-APIAlipayJSBridge.call()
大厂自研window.NativeBridge、window.AppInterface;方法也可能叫 send()、postMessage()、execute()

二、通信协议(所有实现共用)

本质是事件驱动的异步通信:两端按约定交换 JSON 消息,用 callbackId 把「请求」和「响应」配对。

规范说明
命名空间「模块:方法」,如 geo:getlocation
参数编码JSON 传递结构化数据
回调标识callbackId 匹配请求与响应

每条消息通常包含三个字段:

字段作用
handlerName要调用的方法名。接收方按这个名字查找本地是否 registerHandler 过
data业务参数
callbackId可选。有则处理完后按 ID 回传,对应发起方那次调用的回调

Web → Native

JS 生成 message 入队 → 触发 URL 变化或调用注入对象 → Native 拦截 / 分发 → 按 callbackId 返回结果。

Native → Web

Native 通过 evaluateJavaScript(或已注册的 Handler)执行 JS → 获取返回值或触发回调。

协议是对称的:无论谁发起,底层都是「按名字找处理器 + 按 ID 回传」。


三、两种实现方式

1. 总览

方式怎么通适合
URL 拦截构造约定 URL(常用隐藏 iframe 设 src),Native 拦截后解析轻量、兼容优先、无需注入对象
代码注入Native 向页面注入 JS 对象,前端直接调方法复杂参数、高频调用、原生主动推事件
MessageHandlersiOS WKWebView 的 window.webkit.messageHandlers属于注入 / 通道类,是 iOS 官方推荐通道

2. URL 拦截

优点

  • 门槛低:不必改 WebView 内核或注入对象,拦截 URL 即可。
  • 兼容性好:多数系统版本和 WebView 都能用。
  • 安全性可控:约定协议 + 黑白名单 / URL 校验。
  • 好调试:抓包即可看到通信内容。

缺点

  • 能力受限:主要靠 URL 参数,难传大对象、函数。
  • 性能开销大:每次通信一次 URL 请求,高频调用容易卡顿。
  • 可能被绕过:Web 端用非常规方式构造请求时,拦截可能失效。

3. 代码注入

优点

  • 能力强:注入原生对象,支持复杂数据、同步 / 异步回调。
  • 性能好:内存里做方法映射,没有额外「伪网络请求」。
  • 易扩展:可动态注入方法,封装更多原生能力。

缺点

  • 实现复杂:要处理注入时机、系统版本、作用域隔离。
  • 安全风险高:注入环境权限管控不严,可能被恶意脚本利用。
  • 难调试:发生在 WebView 内部,常规抓包看不到过程。

4. 选型

  • 轻量、低频:优先 URL 拦截,落地快、更稳。
  • 复杂业务、高频、需要原生主动推事件:优先代码注入。

四、平台落地:Android 与 iOS

同一套协议,两端用不同 API 接。

Android

方式说明
addJavascriptInterface4.2 以上需 @JavascriptInterface,把 Java 对象注入页面供 JS 调用(代码注入)
URL 拦截shouldOverrideUrlLoading 拦截 iframe.src 触发的请求
onJsPrompt 拦截劫持 window.prompt 通信,避免占用 alert / console
evaluateJavascript4.4 以后,Native 调 JS,效率更高且可拿到返回值

iOS

方式说明
WKWebView URL 拦截decidePolicyForNavigationAction(导航请求)或 decidePolicyForNavigationResponse(响应)拦截 URL
messageHandlerswindow.webkit.messageHandlers 注册消息处理器
evaluateJavaScriptNative 调 JS,支持异步回调

常见运行环境差异

  • 微信:标准 JS-SDK(wx.ready)
  • X5 内核:自定义协议(如 tbs://invoke)
  • 通用 WebView:有注入对象走代码注入,否则降级到 URL 拦截

五、常见库:JsBridge 与 DSBridge

两者都是对 JSBridge 协议的封装。JsBridge 走 callHandler / registerHandler;DSBridge 走自己的 dsBridge.call。

对比维度JsBridgeDSBridge
代表仓库lzyzsd/JsBridgewendux/DSBridge-Android、DSBridge-IOS
平台Android(H5 API 与 iOS 的 WebViewJavascriptBridge 对齐,两端可共用同一套 JS)官方 Android + iOS
前端入口window.WebViewJavascriptBridgedsBridge
调用风格只有异步:callHandler / registerHandler同步 dsBridge.call(name, arg) + 异步(带 callback)
额外能力基础双向调用、回调匹配命名空间、API Object、Progress Callback(一次调用多次回传)
正式发版JitPack 常用 1.0.4iOS 3.0.6(2018-11);npm dsbridge 3.1.4(2018-09)
维护现状官方很少动,有社区 fork约 2019 年后基本停更

选型:

  • H5 已写 callHandler / registerHandler,只要基础双向调用 → JsBridge(iOS 用同 API 的 WebViewJavascriptBridge)。
  • 需要同步返回值、命名空间、进度多次回传 → DSBridge。
  • 两边官方都已停更;新项目更常见的是跟平台走(WKWebView messageHandlers、Android JavascriptInterface)或自研薄封装。

六、前端怎么用

1. 先等待桥就绪

H5 脚本执行 和 Native 注入是异步的。桥还没挂上就 callHandler 会报错。

常见写法:已存在则立刻用;否则监听 WebViewJavascriptBridgeReady。

/**
 * 等待 Native 把桥注入完成后再执行业务回调。
 *
 * 坑:若 Native 已注入且 Ready 事件已经派发过,
 * 后挂上的 addEventListener 收不到该事件。
 * 只监听事件可能永久挂起;兜底做法是轮询检测
 * window.WebViewJavascriptBridge 是否存在,并设超时。
 */
function setupWebViewJavascriptBridge(callback) {
  // 桥已存在:Native 注入更早,立刻把桥交给回调
  if (window.WebViewJavascriptBridge) {
    return callback(window.WebViewJavascriptBridge);
  }
  // 桥还不存在:等 Native 注入完成后派发的就绪事件
  document.addEventListener(
    "WebViewJavascriptBridgeReady",
    function () {
      callback(window.WebViewJavascriptBridge);
    },
    false
  );
}
等待策略优点缺点
只听 Ready 事件几乎零延迟、零开销可能漏掉已派发的事件,一直挂起
轮询全局对象不依赖事件,能超时失败最多多等一个 interval

稳妥组合:先看对象在不在 → 再听事件 → 再用短轮询兜底。

2. 代码注入:callHandler(Web → Native)

Native 注入 window.WebViewJavascriptBridge 后,前端直接调对象方法。下面以「发起打印」为例。

setupWebViewJavascriptBridge(function () {
  window.WebViewJavascriptBridge &&
    window.WebViewJavascriptBridge.callHandler(
      "printPaper",        // 1. handlerName:原生已注册的方法名
      JSON.stringify(e),   // 2. data:传给原生的参数(须为字符串)
      function (i) {       // 3. callback:本次一次性回调,i 为 JSON 字符串
        var a = JSON.parse(i);
        200 == a.code ? e.success(a) : e.fail(a);
      }
    );
});
入参本例值含义
1. handlerName"printPaper"要调用的原生方法名,须与 Native 注册名一致
2. dataJSON.stringify(e)桥通常只收字符串,对象要先序列化;无参数时常传 ""
3. callbackfunction (i) { ... }这一次调用的回调。原生返回 JSON 字符串,按 code 走成功 / 失败

3. 代码注入:registerHandler(Native → Web)

前端先注册方法名,原生在任意时刻(打印完成、缺纸、进度)主动通知页面。

setupWebViewJavascriptBridge(function () {
  window.WebViewJavascriptBridge &&
    window.WebViewJavascriptBridge.registerHandler(
      "paperPrinterCallback", // 1. 前端暴露给原生的名字
      function (i, a) {       // 2. 原生来调时执行
        var n = JSON.parse(i);
        e.success(n);
      }
    );
});
入参本例值含义
1. handlerName"paperPrinterCallback"前端暴露给原生的方法名
2. handlerfunction (i, a)长期有效。i 是原生传入的 JSON 字符串;a 是可选的 responseCallback,处理完可再回传给原生

本例双向对照:

API方法名方向含义
callHandlerprintPaperWeb → Native前端主动发起打印
registerHandlerpaperPrinterCallbackNative → Web接收原生后续推送(可多次)

4. 为什么 callHandler 已有回调,还要 registerHandler?

两者不能互相替代。差别在三件事:谁主动、活多久、能否解耦。

结合打印:printPaper 的第三个参数只能回答「这次指令有没有被原生接住」;真正打完、缺纸、进度,由原生在任意时刻推过来,必须靠事先注册的 paperPrinterCallback。

① 通信方向

  • callHandler:H5 主动调原生(相机、定位、打印)。回调只收这一次结果,返回后这次通信结束。
  • 局限:原生若要主动通知 H5(登录态变化、推送、从后台回前台、打印完成),当时往往没有正在进行的 callHandler,也就没有回调可塞。H5 必须提前 registerHandler。
  • 对称点:JS 能 callHandler('printPaper'),前提通常是 Native 已经注册了同名 Handler;没注册,Native 不知道跑哪段代码。

② 生命周期

  • callHandler 的回调是一次性的:存在以 callbackId 为键的临时字典里,执行后删除。再点一次按钮要重新 callHandler。
  • registerHandler 是持久的:页面不刷新就一直在,原生可多次触发。例如「摇一摇」应在加载时 registerHandler('onShake', callback),而不是每次摇晃都等 H5 先发一次 callHandler。

③ 异步解耦

原生收到 WebSocket 推送、要更新 H5 红点时,H5 并没有发起请求,原生手里没有回调引用。正确做法:

  1. H5 先 registerHandler('updateBadge', function (count) { ... })
  2. 原生收到推送后主动 callHandler('updateBadge', count)(此时原生是发起方)

对照(记住这一张表即可)

特性callHandler 里的回调registerHandler
触发时机发起方调用后,对方处理完毕时对方随时可触发(知道 handlerName 即可)
生命周期一次性,执行后销毁持久,直到页面销毁或手动移除
主要用途单次操作结果(支付、指令是否被接收)暴露接口;监听对方主动事件
主动性被动接收本次结果能力提供方 / 长期监听
本例printPaper 的第三参paperPrinterCallback

结论:

  • callHandler 的回调:让发起方拿到本次操作的结果。
  • registerHandler:让接收方暴露能力,方便对方在任意时刻主动调用。

仅靠一次性回调,做不了原生对 H5 的主动通知和复杂状态同步。

5. URL 拦截写法(对照)

不依赖注入对象,用隐藏 iframe 发约定 URL,避免页面跳转。

function callNativeByUrl(module, method, params, callbackId) {
  const query = [
    "params=" + encodeURIComponent(JSON.stringify(params || {})),
    "callbackId=" + encodeURIComponent(callbackId || "")
  ].join("&");

  // 约定协议:jsbridge://模块/方法?params=...&callbackId=...
  const url = "jsbridge://" + module + "/" + method + "?" + query;

  const iframe = document.createElement("iframe");
  iframe.style.display = "none";
  iframe.src = url;
  document.documentElement.appendChild(iframe);
  setTimeout(function () {
    iframe.parentNode && iframe.parentNode.removeChild(iframe);
  }, 0);
}

callNativeByUrl("geo", "getlocation", { timeout: 5000 }, "cb_1001");
callNativeByUrl("nav", "backHome", {}, "cb_1002");

原生处理完后,用 evaluateJavaScript 调前端按 callbackId 挂好的函数:

window.JSBridgeCallbacks = window.JSBridgeCallbacks || {};

window.JSBridgeCallbacks.cb_1001 = function (response) {
  console.log("定位结果", response);
};

// Native 侧等价:
// window.JSBridgeCallbacks.cb_1001({"code":200,"data":{"lat":36.7,"lng":119.1}})

七、一张表收口

对比项代码注入(callHandler / registerHandler)URL 拦截
入口window.WebViewJavascriptBridge 这类注入对象隐藏 iframe / 改 location 为约定 URL
传参JSON 字符串或对象URL query,需 encodeURIComponent
回调callHandler 第三参;registerHandler 的 responseCallback前端按 callbackId 挂函数,Native evaluateJavaScript 触发
Native 主动推事件靠 H5 事先 registerHandler同样靠预先挂好的全局回调函数
适用复杂参数、高频、双向事件轻量、兼容优先、无需注入对象

记忆口诀

  1. JSBridge 是协议,某个 window.xxx 只是入口。
  2. 消息三件套:handlerName + data + callbackId。
  3. 实现两条路:URL 拦截(兼容) / 代码注入(能力与性能)。
  4. callHandler 问「这次怎么样」;registerHandler 说「以后有事找我」。