JSBridge 基础知识

JSBridge

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

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

知识地图

ini 复制代码
概念层    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 差异,注入脚本,暴露 callHandlerregisterHandler
window.WebViewJavascriptBridge 调用入口 页面里实际调的那个全局对象

可类比为:

JSBridge ≈ HTTP;WebViewJavascriptBridge ≈ Axios;callHandleraxios.post()

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

实现 全局对象 / 典型 API
WebViewJavascriptBridge window.WebViewJavascriptBridge.callHandler()
微信 JS-SDK window.wx,如 wx.invoke()wx.ready()
支付宝 JS-API AlipayJSBridge.call()
大厂自研 window.NativeBridgewindow.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 对象,前端直接调方法 复杂参数、高频调用、原生主动推事件
MessageHandlers iOS WKWebView 的 window.webkit.messageHandlers 属于注入 / 通道类,是 iOS 官方推荐通道

2. URL 拦截

优点

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

缺点

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

3. 代码注入

优点

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

缺点

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

4. 选型

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

四、平台落地:Android 与 iOS

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

Android

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

iOS

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

常见运行环境差异

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

五、常见库:JsBridge 与 DSBridge

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

对比维度 JsBridge DSBridge
代表仓库 lzyzsd/JsBridge wendux/DSBridge-AndroidDSBridge-IOS
平台 Android(H5 API 与 iOS 的 WebViewJavascriptBridge 对齐,两端可共用同一套 JS) 官方 Android + iOS
前端入口 window.WebViewJavascriptBridge dsBridge
调用风格 只有异步:callHandler / registerHandler 同步 dsBridge.call(name, arg) + 异步(带 callback)
额外能力 基础双向调用、回调匹配 命名空间、API Object、Progress Callback(一次调用多次回传)
正式发版 JitPack 常用 1.0.4 iOS 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

javascript 复制代码
/**
 * 等待 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 后,前端直接调对象方法。下面以「发起打印」为例。

javascript 复制代码
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. data JSON.stringify(e) 桥通常只收字符串,对象要先序列化;无参数时常传 ""
3. callback function (i) { ... } 这一次 调用的回调。原生返回 JSON 字符串,按 code 走成功 / 失败

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

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

javascript 复制代码
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. handler function (i, a) 长期有效。i 是原生传入的 JSON 字符串;a 是可选的 responseCallback,处理完可再回传给原生

本例双向对照:

API 方法名 方向 含义
callHandler printPaper Web → Native 前端主动发起打印
registerHandler paperPrinterCallback Native → 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,避免页面跳转。

javascript 复制代码
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 挂好的函数:

javascript 复制代码
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 第三参;registerHandlerresponseCallback 前端按 callbackId 挂函数,Native evaluateJavaScript 触发
Native 主动推事件 靠 H5 事先 registerHandler 同样靠预先挂好的全局回调函数
适用 复杂参数、高频、双向事件 轻量、兼容优先、无需注入对象

记忆口诀

  1. JSBridge 是协议,某个 window.xxx 只是入口。
  2. 消息三件套:handlerName + data + callbackId
  3. 实现两条路:URL 拦截(兼容) / 代码注入(能力与性能)。
  4. callHandler 问「这次怎么样」;registerHandler 说「以后有事找我」。
相关推荐
BigTopOne1 小时前
Ubuntu 虚拟机编译 WebRTC Android AAR(M140 / branch-heads/7339)
前端
默_笙1 小时前
🍕 后端接口还没写好,前端已经跑起来了?Mock 数据 + axios 接口层,前后端再也不互相"等"
前端·javascript
书源1 小时前
AI 时代写给前端同行:什么在贬值,什么在涨价
前端·程序员·ai编程
爱勇宝1 小时前
客户只想看个页面,我却做了一个静态演示发布系统
前端·javascript·后端
渔夫正在掘金1 小时前
告别构建时代:为什么 AI 编程浪潮正让 Vue 与 React 走向落后?
前端
一个有理想的摸鱼选手1 小时前
(四)路书Agnet-综合天气距离交通节奏等多因素来编排旅行路线
前端·后端·gis
神奇的程序员1 小时前
在这个独属于ai的时代,我终究是被裁员了
前端·后端·面试
玉宇夕落1 小时前
React + JWT 登录鉴权系统
前端
hunterandroid2 小时前
[鸿蒙从零到一] HarmonyOS 安全加固与数据保护实战:从密钥管理到反调试的全链路防护
前端