JSBridge 原理与实现
实现 Web 与 Native 双向通信的桥接方案
问题
JSBridge 是 Hybrid App 中 Web 与 Native 通信的桥梁。请说明其原理,包括 Native 调用 Web、Web 调用 Native 的实现方式。
解答
整体架构
┌─────────────────────────────────────────┐
│ WebView │
│ ┌─────────────────────────────────┐ │
│ │ JavaScript │ │
│ │ window.JSBridge.call() │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ JSBridge │ │
│ │ (URL Schema / 注入API) │ │
│ └────────────┬────────────┘ │
└─────────────────┼───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Native │
│ (iOS / Android) │
└─────────────────────────────────────────┘
Web 调用 Native
方式一:URL Schema 拦截
// Web 端发起调用
function callNative(method, params, callback) {
// 生成唯一回调 ID
const callbackId = `cb_${Date.now()}_${Math.random().toString(36).slice(2)}`
// 注册回调函数
window.__callbacks = window.__callbacks || {}
window.__callbacks[callbackId] = callback
// 构造 URL Schema
const url = `myapp://${method}?params=${encodeURIComponent(JSON.stringify(params))}&callback=${callbackId}`
// 通过 iframe 发起请求(不会引起页面跳转)
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
document.body.appendChild(iframe)
// 移除 iframe
setTimeout(() => {
document.body.removeChild(iframe)
}, 100)
}
// 使用示例
callNative('share', { title: 'Hello', url: 'https://example.com' }, (result) => {
console.log('分享结果:', result)
})
// iOS 端拦截 URL Schema (WKWebView)
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url,
url.scheme == "myapp" else {
decisionHandler(.allow)
return
}
// 解析方法名和参数
let method = url.host ?? ""
let params = parseQueryParams(url)
// 执行对应的 Native 方法
handleJSCall(method: method, params: params)
decisionHandler(.cancel)
}
方式二:注入 API(推荐)
// Native 端注入全局对象后,Web 端直接调用
// iOS: WKScriptMessageHandler
// Android: addJavascriptInterface
// Web 端封装
class JSBridge {
static call(method, params = {}) {
return new Promise((resolve, reject) => {
const callbackId = `cb_${Date.now()}`
// 注册回调
window.__bridgeCallbacks = window.__bridgeCallbacks || {}
window.__bridgeCallbacks[callbackId] = { resolve, reject }
// 调用 Native 注入的方法
if (window.webkit?.messageHandlers?.nativeBridge) {
// iOS WKWebView
window.webkit.messageHandlers.nativeBridge.postMessage({
method,
params,
callbackId
})
} else if (window.AndroidBridge) {
// Android WebView
window.AndroidBridge.call(JSON.stringify({
method,
params,
callbackId
}))
} else {
reject(new Error('JSBridge not available'))
}
})
}
// 供 Native 调用的回调处理
static _handleCallback(callbackId, result, error) {
const callback = window.__bridgeCallbacks?.[callbackId]
if (callback) {
error ? callback.reject(error) : callback.resolve(result)
delete window.__bridgeCallbacks[callbackId]
}
}
}
// 使用示例
async function share() {
try {
const result = await JSBridge.call('share', {
title: '分享标题',
content: '分享内容'
})
console.log('分享成功', result)
} catch (e) {
console.error('分享失败', e)
}
}
// Android 端注入 API
public class AndroidBridge {
private WebView webView;
@JavascriptInterface
public void call(String jsonStr) {
try {
JSONObject json = new JSONObject(jsonStr);
String method = json.getString("method");
JSONObject params = json.getJSONObject("params");
String callbackId = json.getString("callbackId");
// 处理调用
handleMethod(method, params, callbackId);
} catch (Exception e) {
e.printStackTrace();
}
}
// 执行回调
private void callback(String callbackId, Object result) {
String js = String.format(
"JSBridge._handleCallback('%s', %s, null)",
callbackId,
new Gson().toJson(result)
);
webView.evaluateJavascript(js, null);
}
}
// 注入到 WebView
webView.addJavascriptInterface(new AndroidBridge(), "AndroidBridge");
Native 调用 Web
// Web 端注册方法供 Native 调用
window.JSBridge = window.JSBridge || {}
window.JSBridge.registerHandler = function(name, handler) {
window.JSBridge._handlers = window.JSBridge._handlers || {}
window.JSBridge._handlers[name] = handler
}
// Native 调用此方法
window.JSBridge._invokeHandler = function(name, params, callbackId) {
const handler = window.JSBridge._handlers?.[name]
if (handler) {
const result = handler(params)
// 如果有回调,通知 Native
if (callbackId) {
JSBridge.call('_callback', { callbackId, result })
}
}
}
// 注册处理函数
JSBridge.registerHandler('updateUserInfo', (data) => {
console.log('收到用户信息更新:', data)
// 更新页面
return { success: true }
})
// iOS 调用 Web 方法
func callJS(method: String, params: [String: Any], callback: ((Any?) -> Void)?) {
let paramsJson = try? JSONSerialization.data(withJSONObject: params)
let paramsStr = String(data: paramsJson ?? Data(), encoding: .utf8) ?? "{}"
let js = "window.JSBridge._invokeHandler('\(method)', \(paramsStr), '\(callbackId)')"
webView.evaluateJavaScript(js) { result, error in
callback?(result)
}
}
// 使用
callJS(method: "updateUserInfo", params: ["name": "张三", "age": 18]) { result in
print("JS 返回: \(result)")
}
完整的 JSBridge 封装
// bridge.js - 完整的 JSBridge 实现
(function() {
const callbacks = {}
const handlers = {}
let callbackId = 0
const JSBridge = {
// Web 调用 Native
call(method, params = {}) {
return new Promise((resolve, reject) => {
const id = `cb_${++callbackId}`
callbacks[id] = { resolve, reject }
const message = { method, params, callbackId: id }
if (window.webkit?.messageHandlers?.bridge) {
window.webkit.messageHandlers.bridge.postMessage(message)
} else if (window.AndroidBridge?.postMessage) {
window.AndroidBridge.postMessage(JSON.stringify(message))
} else {
// 降级到 URL Schema
this._callBySchema(message)
}
// 超时处理
setTimeout(() => {
if (callbacks[id]) {
callbacks[id].reject(new Error('Bridge call timeout'))
delete callbacks[id]
}
}, 10000)
})
},
// URL Schema 降级方案
_callBySchema(message) {
const url = `myapp://bridge?data=${encodeURIComponent(JSON.stringify(message))}`
const iframe = document.createElement('iframe')
iframe.style.cssText = 'display:none;width:0;height:0;'
iframe.src = url
document.body.appendChild(iframe)
setTimeout(() => iframe.remove(), 200)
},
// 注册供 Native 调用的方法
register(name, handler) {
handlers[name] = handler
},
// Native 调用 Web(由 Native 触发)
_invoke(name, params, cbId) {
const handler = handlers[name]
if (handler) {
Promise.resolve(handler(params)).then(result => {
if (cbId) {
this.call('_nativeCallback', { callbackId: cbId, result })
}
})
}
},
// Native 回调 Web(由 Native 触发)
_callback(id, result, error) {
const cb = callbacks[id]
if (cb) {
error ? cb.reject(new Error(error)) : cb.resolve(result)
delete callbacks[id]
}
}
}
window.JSBridge = JSBridge
})()
关键点
- URL Schema 拦截:通过 iframe 发起自定义协议请求,Native 拦截并解析,兼容性好但效率较低
- 注入 API:Native 向 WebView 注入全局对象,Web 直接调用,效率高且支持同步返回
- 回调机制:通过 callbackId 关联请求和响应,实现异步回调
- Native 调用 Web:通过
evaluateJavaScript执行 JS 代码,调用预先注册的处理函数 - 安全考虑:Android 4.2+ 需要
@JavascriptInterface注解,避免反射漏洞
目录