目录
[一、Hybrid 通信整体概述](#一、Hybrid 通信整体概述)
[2.1 原生调用 H5(App → H5)](#2.1 原生调用 H5(App → H5))
[H5 基础回调示例](#H5 基础回调示例)
[两端原生调用 API 对比](#两端原生调用 API 对比)
[2.2 H5 调用原生(H5 → App)](#2.2 H5 调用原生(H5 → App))
[2.2.1 Android:addJavascriptInterface 对象注入](#2.2.1 Android:addJavascriptInterface 对象注入)
[2.2.2 iOS:WKScriptMessageHandler 消息通道](#2.2.2 iOS:WKScriptMessageHandler 消息通道)
[3.1 Android Kotlin 端实现](#3.1 Android Kotlin 端实现)
[3.2 iOS Swift 端实现](#3.2 iOS Swift 端实现)
[四、通用跨平台 JSBridge 封装(bridge.js)](#四、通用跨平台 JSBridge 封装(bridge.js))
[4.1 设计思路](#4.1 设计思路)
[4.2 完整源码](#4.2 完整源码)
[原生主动回调 H5 执行语句](#原生主动回调 H5 执行语句)
一、Hybrid 通信整体概述
在混合开发(Hybrid App)场景中,H5 页面嵌入 App WebView 后,存在两类核心通信需求:
- H5 主动调用原生:唤起相册、定位、支付、弹窗等 App 原生能力;
- 原生主动回调 H5:原生处理完成业务后,将结果数据回传给页面渲染。
Android、iOS 两套 WebView 底层通信 API 完全不兼容,直接业务层写分支判断会造成大量冗余代码。本文从底层原理出发,实现一套屏蔽平台差异、支持 Promise 异步、可直接上生产的通用 JSBridge 方案。
二、两大通信方向底层原理
2.1 原生调用 H5(App → H5)
通信原理
H5 提前在 window 全局挂载约定名称的回调函数,Android /iOS 通过 WebView 提供的 evaluateJavascript / evaluateJavaScript 动态执行 JS 代码,携带业务数据调用全局函数。
H5 基础回调示例
javascript
// 防止页面重复注册覆盖函数
if (!window.nativeCallback) {
/**
* 原生统一回调入口
* @param {string|object} res 原生返回数据
*/
window.nativeCallback = function (res) {
// 兼容 Android JSON字符串 / iOS JS对象两种格式
const data = typeof res === 'string' ? JSON.parse(res) : res;
console.log('接收原生回调数据:', data);
}
}
两端原生调用 API 对比
| 平台 | 核心 API | 调用示例 |
|---|---|---|
| Android | evaluateJavascript | webView.evaluateJavascript("window.nativeCallback('{\"code\":0}')", null) |
| iOS | evaluateJavaScript | webView.evaluateJavaScript("window.nativeCallback({\"code\":0})") |
2.2 H5 调用原生(H5 → App)
2.2.1 Android:addJavascriptInterface 对象注入
- 原生通过
addJavascriptInterface将 Java/Kotlin 对象注入 H5 window; - H5 通过
window.[注入别名].[方法名]()调用原生逻辑; - 限制:参数仅支持字符串,H5 必须手动
JSON.stringify序列化对象; - 强制要求:暴露给 JS 的方法必须添加
@JavascriptInterface安全注解。
2.2.2 iOS:WKScriptMessageHandler 消息通道
- WKWebView 注册
WKUserContentController消息处理器; - H5 使用系统内置固定 API:
window.webkit.messageHandlers.[名称].postMessage(); - 优势:可直接传递 JS 对象,无需强制转 JSON 字符串;
- 限制:仅 WKWebView 支持,废弃 UIWebView 无此能力。
三、双平台原生完整实现代码
3.1 Android Kotlin 端实现
Kotlin
import android.webkit.JavascriptInterface
import android.webkit.WebView
import com.google.gson.Gson
class WebBridge(private val webView: WebView) {
private val gson = Gson()
init {
// 将原生对象注入window,H5通过 window.NativeBridge 访问
webView.addJavascriptInterface(BridgeObj(), "NativeBridge")
}
inner class BridgeObj {
/**
* H5调用获取用户信息
* @param jsonStr H5传递的JSON字符串参数
*/
@JavascriptInterface
fun getUserInfo(jsonStr: String) {
// 解析H5参数
val params = gson.fromJson(jsonStr, Map::class.java)
println("收到H5请求:$params")
// 模拟业务逻辑,组装返回数据
val result = mapOf(
"code" to 0,
"data" to mapOf("name" to "张三", "userId" to 10001)
)
val callbackJson = gson.toJson(result)
// 调用H5全局回调,回传数据
webView.evaluateJavascript("window.Bridge.onCallback('cb_get_user', '$callbackJson')", null)
}
/**
* H5调用弹出Toast提示
*/
@JavascriptInterface
fun showToast(jsonStr: String) {
val params = gson.fromJson(jsonStr, Map::class.java)
val msg = params["message"] ?: ""
println("Toast提示:$msg")
}
}
}
3.2 iOS Swift 端实现
Swift
import WebKit
class WebViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
// 注册消息处理器,名称与H5约定 NativeBridge
config.userContentController.add(self, name: "NativeBridge")
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
}
// 接收H5 postMessage 消息统一入口
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard message.name == "NativeBridge",
let payload = message.body as? [String: Any],
let funcName = payload["funcName"] as? String else { return }
switch funcName {
case "getUserInfo":
let callbackId = payload["__callbackId__"] as! String
let res: [String: Any] = [
"code": 0,
"data": ["name": "张三", "userId": 10001]
]
// 回调H5
let jsonData = try! JSONSerialization.data(withJSONObject: res)
let jsonStr = String(data: jsonData, encoding: .utf8)!
webView.evaluateJavaScript("window.Bridge.onCallback('\(callbackId)', '\(jsonStr)')")
case "showToast":
guard let msg = payload["message"] as? String else { return }
print("Toast提示:\(msg)")
default: break
}
}
}
四、通用跨平台 JSBridge 封装(bridge.js)
4.1 设计思路
- 使用 IIFE 闭包隔离内部变量,不污染全局;
- UA 自动识别 Android /iOS/macOS / Windows / Linux 环境;
- 底层统一分发双端通信逻辑,业务层无需区分平台;
- 基于 CallbackId + Promise 实现异步回调,解决回调地狱;
- 全局缓存回调函数,原生回调后自动销毁,避免内存泄漏;
- 兼容 CommonJS 打包与浏览器直接引入两种使用方式。
4.2 完整源码
javascript
/**
* 跨平台通用 JSBridge 通信模块
* 兼容 Android / iOS / macOS / Windows / Linux
* 能力:同步调用、Promise异步回调、环境检测、原生回调分发
*/
const Bridge = (() => {
// 1. 识别当前运行系统
const detectOS = () => {
const ua = navigator.userAgent.toLowerCase();
const platform = navigator.platform.toLowerCase();
if (/android/i.test(ua)) return 'android';
if (/iphone|ipad|ipod/i.test(ua)) return 'ios';
if (/macintosh|macintel/i.test(platform)) return 'macos';
if (/win32|windows/i.test(platform)) return 'windows';
if (/linux/i.test(platform) && !/android/i.test(ua)) return 'linux';
return 'unknown';
};
// 2. 底层分发:统一调用原生方法
const _invokeNative = (objName, funcName, data) => {
const os = detectOS();
const jsonString = typeof data === 'string' ? data : JSON.stringify(data);
// Android / Windows / Linux 注入对象调用
if (['android', 'windows', 'linux'].includes(os)) {
const nativeObj = window[objName];
if (nativeObj && typeof nativeObj[funcName] === 'function') {
nativeObj[funcName](jsonString);
return true;
}
}
// iOS / macOS webkit messageHandler
if (['ios', 'macos'].includes(os)) {
const handler = window.webkit?.messageHandlers?.[objName];
if (handler && typeof handler.postMessage === 'function') {
handler.postMessage(jsonString);
return true;
}
}
console.warn(`[Bridge Warn] 当前环境${os}不支持 ${objName}.${funcName}`);
return false;
};
// 对外暴露API
return {
/**
* 同步单向调用原生,无需返回结果
* @param {string} objName 桥接对象名
* @param {string} funcName 原生方法名
* @param {object|string} data 请求参数
* @returns {boolean} 是否成功发起调用
*/
invoke(objName, funcName, data = {}) {
return _invokeNative(objName, funcName, data);
},
/**
* 异步调用原生,Promise 返回业务结果
* @param {string} objName
* @param {string} funcName
* @param {object} data
* @param {string} [callbackId] 自定义回调标识,不传自动生成
* @returns {Promise<any>}
*/
invokeAsync(objName, funcName, data = {}, callbackId) {
return new Promise((resolve, reject) => {
// 生成唯一回调ID
const payload = {
...data,
funcName,
__callbackId__: callbackId || `cb_${Date.now()}_${Math.random().toString(36).slice(2)}`
};
// 全局缓存Promise回调
window.__bridge_callbacks__ = window.__bridge_callbacks__ || {};
window.__bridge_callbacks__[payload.__callbackId__] = { resolve, reject };
const invokeSuccess = _invokeNative(objName, funcName, payload);
if (!invokeSuccess) {
delete window.__bridge_callbacks__[payload.__callbackId__];
reject(new Error('Native Bridge 环境不存在'));
}
});
},
/**
* 原生统一回调入口,由App主动执行
* @param {string} callbackId 异步请求唯一标识
* @param {string|object} resultJson 原生返回数据
*/
onCallback(callbackId, resultJson) {
const cbCache = window.__bridge_callbacks__?.[callbackId];
if (!cbCache) return;
// 统一格式化返回数据
const result = typeof resultJson === 'string' ? JSON.parse(resultJson) : resultJson;
// 约定协议:code=0 成功,其余失败
if (result.code === 0 || result.success) {
cbCache.resolve(result.data);
} else {
cbCache.reject(result);
}
// 销毁缓存,防止内存泄漏
delete window.__bridge_callbacks__[callbackId];
},
/** 获取当前系统标识 */
getOS: detectOS
};
})();
// 兼容打包工具导出
if (typeof module !== 'undefined' && module.exports) {
module.exports = Bridge;
}
五、业务层实战调用示例(main.js)
javascript
import Bridge from './bridge.js';
// 同步单向调用(仅通知原生,不需要返回)
function showNativeToast() {
Bridge.invoke('NativeBridge', 'showToast', {
message: '操作成功',
duration: 2000
});
}
// Promise 异步调用,等待原生返回数据
async function fetchUserInfo() {
try {
const user = await Bridge.invokeAsync(
'NativeBridge',
'getUserInfo',
{},
'cb_get_user'
);
console.log('获取用户信息成功:', user);
} catch (err) {
console.error('获取用户信息失败:', err);
}
}
// 平台差异化UI适配
function adaptSafeArea() {
if (Bridge.getOS() === 'ios') {
// iPhone底部小黑条安全区适配
document.body.style.paddingBottom = '34px';
}
}
// 页面初始化执行
document.addEventListener('DOMContentLoaded', () => {
adaptSafeArea();
showNativeToast();
fetchUserInfo();
});
原生主动回调 H5 执行语句
App 处理完业务后,执行下面 JS 将结果回传给页面 Promise:
javascript
// Android / iOS 统一执行脚本示例
window.Bridge.onCallback("cb_get_user", '{"code":0,"data":{"name":"张三","userId":10001}}')
六、生产环境避坑注意事项
- Android 安全强制注解 所有暴露给 JS 的方法必须添加
@JavascriptInterface,Android 4.2+ 无注解会直接拦截调用,出现无响应。 - 数据格式兼容问题 Android 仅支持字符串参数,H5 调用原生方法必须
JSON.stringify;iOS postMessage 可直接传对象,但为双端统一建议全部序列化。 - 回调内存泄漏 异步请求回调执行完成后必须删除全局缓存的 resolve/reject,页面频繁刷新、多次请求会造成内存堆积。
- 命名强约定 H5 与原生的桥对象名、方法名、回调字段
__callbackId__必须完全一致,大小写敏感。 - WebView 生命周期 页面销毁前清空全局
window.__bridge_callbacks__,避免页面卸载后原生回调空白页面报错。 - iOS 废弃 UIWebView UIWebView 无
webkit.messageHandlers通道,必须使用 WKWebView。
七、全文总结
- 原生调 H5:依靠 WebView 执行 JS,调用 window 全局挂载的回调函数;
- H5 调原生:Android 对象注入、iOS WKWebView 消息通道,两套底层 API 完全隔离;
- 工程化方案 :通过 JSBridge 闭包封装屏蔽平台差异,对外提供统一
invoke/invokeAsync; - 异步闭环 :CallbackId 映射 Promise 缓存,原生通过统一
onCallback分发结果,替代传统多层回调; - 落地价值:一套 JS 代码同时兼容双端,业务层无需写大量平台判断,可直接投入线上生产。