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}&params=${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 NativeJS 驱动原生组件较好
Flutter自绘引擎
小程序双线程架构较好

关键点

  • 通信方式:URL Scheme 拦截、注入全局对象、evaluateJavaScript
  • JSBridge:封装双向通信,支持回调和事件监听
  • 异步处理:通过 callbackId 匹配请求和响应
  • 兼容性:需要区分 Android/iOS,做好降级处理
  • 安全性:校验 URL Scheme 来源,防止恶意调用