浏览器原理 · 40/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

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 注解,避免反射漏洞