hybrid开发js与原生交互的原理 简单说Hybrid 的 JS 和原生通信本质上就是两边约定一套“消息传递机制”。JS 负责发消息给原生原生负责执行能力原生也可以反过来通知 JS。通常分成两类JS 调原生原生调 JS一、先理解 Hybrid 是什么Hybrid App 一般是外层是原生 App中间放一个WebView页面内容是H5 / JS / CSS也就是页面 UI、业务逻辑很多是前端写的摄像头、定位、支付、文件、通知等能力由原生提供因为 JS 本身拿不到很多系统能力所以就需要和原生通信。二、整体架构图可以理解成这样H5/JS ---- WebView桥接层 ---- Native(iOS/Android)其中的关键就是“桥接层”也常叫JSBridgeWebView BridgeHybrid Bridge三、JS 调原生的原理JS 调原生常见有这几种方式。1. 通过拦截 URL Scheme这是早期很常见的方案。JS 发起一个特殊 URLlocation.href jsbridge://openCamera?callback123原生监听 WebView 的 URL 变化如果发现不是正常网页地址而是自定义协议jsbridge://就拦截下来不让页面跳转而是解析命令方法名openCamera参数callback123然后原生执行对应能力。原理步骤JS 构造一个特殊 URLWebView 准备加载这个 URL原生拦截请求原生解析方法名和参数执行原生能力优点实现简单兼容早期方案缺点URL 长度有限传复杂参数麻烦会有“假跳转”问题调用体验不够优雅2. 通过 WebView 提供的注入对象这是现在更常见、更正规的方案。原生会往 WebView 里注入一个全局对象JS 可直接调用。Android 常见方式Android 里可以通过addJavascriptInterface暴露对象给 JSwindow.NativeBridge.openCamera()JS 看起来像在调一个普通对象方法但实际上底层会转发到 Android 原生代码。iOS 常见方式iOS WKWebView 里常通过window.webkit.messageHandlers.xxx.postMessage(data)比如window.webkit.messageHandlers.openCamera.postMessage({ sourceType: album })原生收到消息后执行对应逻辑。优点调用更自然参数传递更方便性能和可维护性更好缺点iOS 和 Android 方式不统一一般还要再封装一层统一 API3. 统一封装成 JSBridge实际项目里前端不会直接写一堆平台判断而是会封装统一调用方式bridge.invoke(openCamera, { type: front })内部再区分iOS 走window.webkit.messageHandlersAndroid 走注入对象旧环境可能走location.href这样业务代码不关心底层细节。四、原生调 JS 的原理原生调 JS本质上是原生主动在 WebView 中执行一段 JavaScript 代码比如原生可以执行window.onNativeCallback window.onNativeCallback({ code: 0 })或者直接调用某个全局函数handleLoginResult({ token: xxx })常见方式1. 调用 JS 全局函数JS 先定义函数window.handleNativeMessage function(data) { console.log(收到原生消息, data) }原生在适当时机执行window.handleNativeMessage({...})2. 派发浏览器事件原生执行window.dispatchEvent(new CustomEvent(native-message, { detail: { type: paySuccess } }))前端监听window.addEventListener(native-message, (e) { console.log(e.detail) })这种方式更利于解耦。3. Promise / 回调机制回传结果例如 JS 调原生时带一个 callbackIdbridge.invoke(chooseImage, { count: 1 }).then(res { console.log(res) })底层过程是JS 发起调用生成一个callbackId原生执行完成后原生再调用 JS 某个回调分发函数JS 根据callbackId找到对应 Promise 的resolve五、为什么需要 callbackId因为很多原生能力是异步的比如拍照定位支付登录选择文件JS 调用时不能立刻拿到结果所以需要建立“请求-响应”对应关系。例如{ method: getLocation, params: {}, callbackId: cb_1001 }原生处理完后回传{ callbackId: cb_1001, result: { lng: 120, lat: 30 } }这样 JS 就知道这条响应属于哪次调用。六、一个简单的桥接设计思路1. JS 侧封装const callbacks {} let id 0 function invoke(method, params {}) { return new Promise((resolve, reject) { const callbackId cb_${id} callbacks[callbackId] { resolve, reject } const message { method, params, callbackId } // 发给原生 if (window.webkit?.messageHandlers?.NativeBridge) { window.webkit.messageHandlers.NativeBridge.postMessage(message) } else if (window.NativeBridge?.postMessage) { window.NativeBridge.postMessage(JSON.stringify(message)) } else { reject(new Error(当前环境不支持 native bridge)) } }) } window.__nativeCallback__ function(response) { const { callbackId, result, error } response const cb callbacks[callbackId] if (!cb) return if (error) { cb.reject(error) } else { cb.resolve(result) } delete callbacks[callbackId] }2. 业务调用invoke(getLocation).then(res { console.log(定位结果, res) })3. 原生收到消息后原生拿到{ method: getLocation, params: {}, callbackId: cb_1 }执行定位后再回调 JSwindow.__nativeCallback__({ callbackId: cb_1, result: { lat: 30.123, lng: 120.456 } })七、常见通信流程图JS 调原生JS 发起调用 ↓ 封装消息 method params callbackId ↓ 通过 JSBridge 发给 Native ↓ Native 解析消息 ↓ 执行原生能力 ↓ Native 回调 JS ↓ JS 根据 callbackId 找到对应 Promise ↓ resolve / reject八、为什么说 WebView 是关键因为 Hybrid 页面不是直接跑在浏览器里而是跑在Android WebViewiOS WKWebView / UIWebView旧WebView 提供了JS 执行环境页面加载能力原生和 JS 的桥接接口所以 Hybrid 交互的基础其实就是WebView 提供的能力。九、实际项目里的标准做法实际工作中一般不会裸写window.webkit.messageHandlers.xxx而是做一层统一 SDK。例如nativeSDK.openCamera() nativeSDK.getLocation() nativeSDK.pay(data) nativeSDK.closePage()内部统一处理平台判断参数序列化callbackId 管理Promise 化错误处理超时处理版本兼容十、需要解决的几个核心问题1. 平台差异iOS 和 Android 的桥接方式不完全一样要统一封装。2. 异步回调管理需要callbackId或事件机制保证请求和响应一一对应。3. 参数序列化复杂对象一般会转 JSON做字段约定做数据校验4. 安全问题如果桥暴露太多能力网页可能会滥用原生接口所以要做域名白名单接口权限控制参数校验签名/鉴权特别是addJavascriptInterface在旧 Android 上有过安全风险实际要注意版本和暴露范围。5. 时机问题有时候 H5 代码执行时桥对象还没注入完成所以常会有document.addEventListener(BridgeReady, () { // bridge 已就绪再调用 })或者封装成 ready 方法bridge.ready(() { bridge.invoke(getUserInfo) })十一、为什么很多框架都要封装 bridge因为直接写底层调用会有很多问题接口不统一回调难管理错误处理混乱平台判断到处都是不利于维护和扩展所以会抽象出invokeJS 调原生register原生调 JSready桥就绪close/off取消监听十二、一个简化版 JSBridge 示例class JSBridge { constructor() { this.callbacks {} this.callbackId 0 } invoke(method, params {}) { return new Promise((resolve, reject) { const callbackId cb_${this.callbackId} this.callbacks[callbackId] { resolve, reject } const data { method, params, callbackId } if (window.webkit?.messageHandlers?.NativeBridge) { window.webkit.messageHandlers.NativeBridge.postMessage(data) } else if (window.NativeBridge?.postMessage) { window.NativeBridge.postMessage(JSON.stringify(data)) } else { reject(new Error(bridge not found)) } }) } handleCallback(response) { const { callbackId, result, error } response const callback this.callbacks[callbackId] if (!callback) return if (error) { callback.reject(error) } else { callback.resolve(result) } delete this.callbacks[callbackId] } } const bridge new JSBridge() window.__nativeCallback__ function(response) { bridge.handleCallback(response) } export default bridge调用bridge.invoke(chooseImage, { count: 1 }).then(res { console.log(res) })十三、面试怎么答可以这样回答Hybrid 开发中JS 和原生交互的核心原理是通过 WebView 提供的桥接机制完成通信。JS 调原生时通常会通过 URL Scheme、原生注入对象或 WKWebView 的 messageHandler 把方法名、参数和 callbackId 传给 Native原生解析后执行对应能力。原生调 JS 时本质上是在 WebView 中执行一段 JavaScript 代码调用 JS 全局函数或派发事件把结果回传给前端。为了管理异步调用通常会用 callbackId 或 Promise 机制建立请求和响应的映射关系。实际项目里一般会封装一层 JSBridge屏蔽 iOS 和 Android 的差异。十四、和纯 Web、纯原生的区别纯 Web跑在浏览器里只能用浏览器能力难以直接访问系统级功能纯原生全部由 iOS / Android 开发性能和系统能力最强Hybrid页面主要用 H5 写借助 WebView 嵌入原生容器通过 JSBridge 获取原生能力十五、一句话总结Hybrid 里 JS 与原生交互本质就是基于 WebView 建立一套双向消息通信机制JS 通过 bridge 调原生原生再通过执行 JS 或事件回调把结果返回。