JSBridge 的原理是什么?H5 与原生 App 怎样双向通信?

先理解原理和示例,再练习完整口述与追问。

H5 调原生:原生向 WebView 注入对象(Android addJavascriptInterface、iOS WKScriptMessageHandler 的 window.webkit.messageHandlers),或拦截自定义 URL Scheme。原生调 H5:执行 evaluateJavascript 调用页面上的全局函数。异步回调通过 callbackId 映射实现。安全上必须校验页面来源并限制能力白名单。

早期通过 iframe 加载 jsbridge://method?params 的 URL,由原生拦截解析,有长度限制和消息丢失风险;现在主流使用注入对象,调用更直接。由于通信是异步消息,JS 侧给每次调用生成唯一 id,把回调存进表中;原生处理完后调用 window.__bridgeCallback(id, result),JS 找到对应回调执行并删除,配合超时避免回调永不触发。

原生也可以主动派发事件(如网络变化、返回键),H5 用事件订阅接收。安全方面,任意被加载到 WebView 的页面都能调用注入的方法,所以原生要校验当前页面域名,敏感能力(支付、读取通讯录)需要额外鉴权;Android 4.2 以下的 addJavascriptInterface 有反射漏洞,现代系统需用 @JavascriptInterface 注解。

js
const callbacks = new Map()
let seq = 0
function callNative(method, params = {}, timeout = 10000) {
  return new Promise((resolve, reject) => {
    const id = `cb_${Date.now()}_${seq++}`
    const timer = setTimeout(() => { callbacks.delete(id); reject(new Error(`${method} 调用超时`)) }, timeout)
    callbacks.set(id, { resolve, reject, timer })
    const message = JSON.stringify({ id, method, params })
    if (window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage(message)
    else if (window.AndroidBridge) window.AndroidBridge.postMessage(message)
    else { clearTimeout(timer); callbacks.delete(id); reject(new Error('不在 App 环境中')) }
  })
}
window.__bridgeCallback = (id, error, data) => {
  const entry = callbacks.get(id)
  if (!entry) return
  clearTimeout(entry.timer)
  callbacks.delete(id)
  error ? entry.reject(new Error(error)) : entry.resolve(data)
}

注入对象在页面脚本执行前可能尚未就绪,需要等待 ready 事件;参数必须序列化,不能传函数或 DOM。H5 在浏览器中打开时要有降级。

H5 调原生主要靠原生向 WebView 注入对象,iOS 用 messageHandlers 的 postMessage,Android 用 addJavascriptInterface,早期也有拦截自定义 URL Scheme 的做法;原生调 H5 是执行 evaluateJavascript 调全局函数。因为是异步消息,JS 侧给每次调用生成 callbackId 存回调,原生处理完带着 id 回调,再加超时。安全上原生要校验页面域名、限制能力白名单,敏感能力额外鉴权。