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 来源,防止恶意调用
目录