浏览器原理
Hybrid 应用开发
Hybrid App 原理、JSBridge 通信机制及实现
问题
什么是 Hybrid App?前端如何与 Native 进行通信?
解答
什么是 Hybrid App
Hybrid App 是混合应用,结合了 Web 技术和 Native 技术。核心是通过 WebView 加载 H5 页面,并通过 JSBridge 实现 JS 与 Native 的双向通信。
┌─────────────────────────────┐
│ Native App │
│ ┌───────────────────────┐ │
│ │ WebView │ │
│ │ ┌─────────────────┐ │ │
│ │ │ H5 页面 │ │ │
│ │ └─────────────────┘ │ │
│ └───────────────────────┘ │
│ ↕ │
│ JSBridge │
│ ↕ │
│ Native 能力 │
└─────────────────────────────┘JS 调用 Native 的方式
1. URL Scheme 拦截
// H5 端发起请求
function callNative(action, params) {
const url = `myapp://action=${action}¶ms=${encodeURIComponent(JSON.stringify(params))}`;
// 方式一:创建 iframe
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(() => document.body.removeChild(iframe), 100);
// 方式二:直接跳转(不推荐,会中断页面)
// location.href = url;
}
// 调用示例:打开相机
callNative('openCamera', { quality: 'high' });2. 注入全局对象
Native 向 WebView 注入全局对象,JS 直接调用:
// Android 注入的对象名通常是 AndroidBridge
// iOS 注入的对象在 window.webkit.messageHandlers
// 封装统一调用方法
function callNative(action, params) {
// Android
if (window.AndroidBridge) {
window.AndroidBridge.postMessage(JSON.stringify({ action, params }));
return;
}
// iOS WKWebView
if (window.webkit?.messageHandlers?.nativeBridge) {
window.webkit.messageHandlers.nativeBridge.postMessage({ action, params });
return;
}
console.warn('Not in Hybrid environment');
}Native 调用 JS
Native 直接执行 JS 代码:
// H5 端暴露全局方法供 Native 调用
window.JSBridge = {
// Native 调用此方法传递数据
onNativeCallback(data) {
console.log('Received from Native:', data);
// 触发对应的回调
const { callbackId, result } = JSON.parse(data);
if (this.callbacks[callbackId]) {
this.callbacks[callbackId](result);
delete this.callbacks[callbackId];
}
},
callbacks: {},
callbackId: 0,
// 调用 Native 并注册回调
call(action, params, callback) {
const id = ++this.callbackId;
this.callbacks[id] = callback;
callNative(action, { ...params, callbackId: id });
}
};完整 JSBridge 封装
class JSBridge {
constructor() {
this.callbacks = {};
this.callbackId = 0;
this.handlers = {};
// 暴露给 Native 调用的入口
window._handleNativeMessage = this.handleNativeMessage.bind(this);
}
// JS 调用 Native
callNative(action, params = {}) {
return new Promise((resolve, reject) => {
const callbackId = ++this.callbackId;
// 设置超时
const timer = setTimeout(() => {
delete this.callbacks[callbackId];
reject(new Error('JSBridge call timeout'));
}, 10000);
this.callbacks[callbackId] = (result) => {
clearTimeout(timer);
if (result.success) {
resolve(result.data);
} else {
reject(new Error(result.message));
}
};
const message = JSON.stringify({ action, params, callbackId });
// 根据环境选择通信方式
if (window.AndroidBridge) {
window.AndroidBridge.postMessage(message);
} else if (window.webkit?.messageHandlers?.nativeBridge) {
window.webkit.messageHandlers.nativeBridge.postMessage(message);
} else {
// 降级到 URL Scheme
this.callByScheme(action, params, callbackId);
}
});
}
// URL Scheme 方式
callByScheme(action, params, callbackId) {
const url = `myapp://${action}?params=${encodeURIComponent(
JSON.stringify({ ...params, callbackId })
)}`;
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 100);
}
// 处理 Native 消息
handleNativeMessage(messageStr) {
try {
const { type, callbackId, data } = JSON.parse(messageStr);
if (type === 'callback' && this.callbacks[callbackId]) {
// Native 回调 JS
this.callbacks[callbackId](data);
delete this.callbacks[callbackId];
} else if (type === 'event' && this.handlers[data.event]) {
// Native 主动推送事件
this.handlers[data.event].forEach(fn => fn(data.payload));
}
} catch (e) {
console.error('Parse native message error:', e);
}
}
// 注册事件监听
on(event, handler) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(handler);
}
off(event, handler) {
if (this.handlers[event]) {
this.handlers[event] = this.handlers[event].filter(fn => fn !== handler);
}
}
}
// 使用示例
const bridge = new JSBridge();
// 调用 Native 方法
bridge.callNative('getDeviceInfo').then(info => {
console.log('设备信息:', info);
});
// 监听 Native 事件
bridge.on('networkChange', (status) => {
console.log('网络状态变化:', status);
});常见 Hybrid 方案对比
| 方案 | 原理 | 性能 | 开发成本 |
|---|---|---|---|
| WebView + JSBridge | 原生 WebView 加载 H5 | 一般 | 低 |
| React Native | JS 驱动原生组件 | 较好 | 中 |
| Flutter | 自绘引擎 | 好 | 中 |
| 小程序 | 双线程架构 | 较好 | 低 |
关键点
- 通信方式:URL Scheme 拦截、注入全局对象、evaluateJavaScript
- JSBridge:封装双向通信,支持回调和事件监听
- 异步处理:通过 callbackId 匹配请求和响应
- 兼容性:需要区分 Android/iOS,做好降级处理
- 安全性:校验 URL Scheme 来源,防止恶意调用