news 2026/7/2 10:56:28

【webview】原生 App 与 H5 双向通信完全指南:JSBridge 原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【webview】原生 App 与 H5 双向通信完全指南:JSBridge 原理与实战

目录

一、Hybrid 通信整体概述

二、两大通信方向底层原理

2.1 原生调用 H5(App → H5)

通信原理

H5 基础回调示例

两端原生调用 API 对比

2.2 H5 调用原生(H5 → App)

2.2.1 Android:addJavascriptInterface 对象注入

2.2.2 iOS:WKScriptMessageHandler 消息通道

三、双平台原生完整实现代码

3.1 Android Kotlin 端实现

3.2 iOS Swift 端实现

四、通用跨平台 JSBridge 封装(bridge.js)

4.1 设计思路

4.2 完整源码

五、业务层实战调用示例(main.js)

原生主动回调 H5 执行语句

六、生产环境避坑注意事项

七、全文总结


一、Hybrid 通信整体概述

在混合开发(Hybrid App)场景中,H5 页面嵌入 App WebView 后,存在两类核心通信需求:

  1. H5 主动调用原生:唤起相册、定位、支付、弹窗等 App 原生能力;
  2. 原生主动回调 H5:原生处理完成业务后,将结果数据回传给页面渲染。

Android、iOS 两套 WebView 底层通信 API 完全不兼容,直接业务层写分支判断会造成大量冗余代码。本文从底层原理出发,实现一套屏蔽平台差异、支持 Promise 异步、可直接上生产的通用 JSBridge 方案。

二、两大通信方向底层原理

2.1 原生调用 H5(App → H5)

通信原理

H5 提前在window全局挂载约定名称的回调函数,Android /iOS 通过 WebView 提供的evaluateJavascript/evaluateJavaScript动态执行 JS 代码,携带业务数据调用全局函数。

H5 基础回调示例
// 防止页面重复注册覆盖函数 if (!window.nativeCallback) { /** * 原生统一回调入口 * @param {string|object} res 原生返回数据 */ window.nativeCallback = function (res) { // 兼容 Android JSON字符串 / iOS JS对象两种格式 const data = typeof res === 'string' ? JSON.parse(res) : res; console.log('接收原生回调数据:', data); } }
两端原生调用 API 对比
平台核心 API调用示例
AndroidevaluateJavascriptwebView.evaluateJavascript("window.nativeCallback('{\"code\":0}')", null)
iOSevaluateJavaScriptwebView.evaluateJavaScript("window.nativeCallback({\"code\":0})")

2.2 H5 调用原生(H5 → App)

2.2.1 Android:addJavascriptInterface 对象注入
  1. 原生通过addJavascriptInterface将 Java/Kotlin 对象注入 H5 window;
  2. H5 通过window.[注入别名].[方法名]()调用原生逻辑;
  3. 限制:参数仅支持字符串,H5 必须手动JSON.stringify序列化对象;
  4. 强制要求:暴露给 JS 的方法必须添加@JavascriptInterface安全注解。
2.2.2 iOS:WKScriptMessageHandler 消息通道
  1. WKWebView 注册WKUserContentController消息处理器;
  2. H5 使用系统内置固定 API:window.webkit.messageHandlers.[名称].postMessage()
  3. 优势:可直接传递 JS 对象,无需强制转 JSON 字符串;
  4. 限制:仅 WKWebView 支持,废弃 UIWebView 无此能力。

三、双平台原生完整实现代码

3.1 Android Kotlin 端实现

import android.webkit.JavascriptInterface import android.webkit.WebView import com.google.gson.Gson class WebBridge(private val webView: WebView) { private val gson = Gson() init { // 将原生对象注入window,H5通过 window.NativeBridge 访问 webView.addJavascriptInterface(BridgeObj(), "NativeBridge") } inner class BridgeObj { /** * H5调用获取用户信息 * @param jsonStr H5传递的JSON字符串参数 */ @JavascriptInterface fun getUserInfo(jsonStr: String) { // 解析H5参数 val params = gson.fromJson(jsonStr, Map::class.java) println("收到H5请求:$params") // 模拟业务逻辑,组装返回数据 val result = mapOf( "code" to 0, "data" to mapOf("name" to "张三", "userId" to 10001) ) val callbackJson = gson.toJson(result) // 调用H5全局回调,回传数据 webView.evaluateJavascript("window.Bridge.onCallback('cb_get_user', '$callbackJson')", null) } /** * H5调用弹出Toast提示 */ @JavascriptInterface fun showToast(jsonStr: String) { val params = gson.fromJson(jsonStr, Map::class.java) val msg = params["message"] ?: "" println("Toast提示:$msg") } } }

3.2 iOS Swift 端实现

import WebKit class WebViewController: UIViewController, WKScriptMessageHandler { var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() let config = WKWebViewConfiguration() // 注册消息处理器,名称与H5约定 NativeBridge config.userContentController.add(self, name: "NativeBridge") webView = WKWebView(frame: view.bounds, configuration: config) view.addSubview(webView) } // 接收H5 postMessage 消息统一入口 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == "NativeBridge", let payload = message.body as? [String: Any], let funcName = payload["funcName"] as? String else { return } switch funcName { case "getUserInfo": let callbackId = payload["__callbackId__"] as! String let res: [String: Any] = [ "code": 0, "data": ["name": "张三", "userId": 10001] ] // 回调H5 let jsonData = try! JSONSerialization.data(withJSONObject: res) let jsonStr = String(data: jsonData, encoding: .utf8)! webView.evaluateJavaScript("window.Bridge.onCallback('\(callbackId)', '\(jsonStr)')") case "showToast": guard let msg = payload["message"] as? String else { return } print("Toast提示:\(msg)") default: break } } }

四、通用跨平台 JSBridge 封装(bridge.js)

4.1 设计思路

  1. 使用 IIFE 闭包隔离内部变量,不污染全局;
  2. UA 自动识别 Android /iOS/macOS / Windows / Linux 环境;
  3. 底层统一分发双端通信逻辑,业务层无需区分平台;
  4. 基于 CallbackId + Promise 实现异步回调,解决回调地狱;
  5. 全局缓存回调函数,原生回调后自动销毁,避免内存泄漏;
  6. 兼容 CommonJS 打包与浏览器直接引入两种使用方式。

4.2 完整源码

/** * 跨平台通用 JSBridge 通信模块 * 兼容 Android / iOS / macOS / Windows / Linux * 能力:同步调用、Promise异步回调、环境检测、原生回调分发 */ const Bridge = (() => { // 1. 识别当前运行系统 const detectOS = () => { const ua = navigator.userAgent.toLowerCase(); const platform = navigator.platform.toLowerCase(); if (/android/i.test(ua)) return 'android'; if (/iphone|ipad|ipod/i.test(ua)) return 'ios'; if (/macintosh|macintel/i.test(platform)) return 'macos'; if (/win32|windows/i.test(platform)) return 'windows'; if (/linux/i.test(platform) && !/android/i.test(ua)) return 'linux'; return 'unknown'; }; // 2. 底层分发:统一调用原生方法 const _invokeNative = (objName, funcName, data) => { const os = detectOS(); const jsonString = typeof data === 'string' ? data : JSON.stringify(data); // Android / Windows / Linux 注入对象调用 if (['android', 'windows', 'linux'].includes(os)) { const nativeObj = window[objName]; if (nativeObj && typeof nativeObj[funcName] === 'function') { nativeObj[funcName](jsonString); return true; } } // iOS / macOS webkit messageHandler if (['ios', 'macos'].includes(os)) { const handler = window.webkit?.messageHandlers?.[objName]; if (handler && typeof handler.postMessage === 'function') { handler.postMessage(jsonString); return true; } } console.warn(`[Bridge Warn] 当前环境${os}不支持 ${objName}.${funcName}`); return false; }; // 对外暴露API return { /** * 同步单向调用原生,无需返回结果 * @param {string} objName 桥接对象名 * @param {string} funcName 原生方法名 * @param {object|string} data 请求参数 * @returns {boolean} 是否成功发起调用 */ invoke(objName, funcName, data = {}) { return _invokeNative(objName, funcName, data); }, /** * 异步调用原生,Promise 返回业务结果 * @param {string} objName * @param {string} funcName * @param {object} data * @param {string} [callbackId] 自定义回调标识,不传自动生成 * @returns {Promise<any>} */ invokeAsync(objName, funcName, data = {}, callbackId) { return new Promise((resolve, reject) => { // 生成唯一回调ID const payload = { ...data, funcName, __callbackId__: callbackId || `cb_${Date.now()}_${Math.random().toString(36).slice(2)}` }; // 全局缓存Promise回调 window.__bridge_callbacks__ = window.__bridge_callbacks__ || {}; window.__bridge_callbacks__[payload.__callbackId__] = { resolve, reject }; const invokeSuccess = _invokeNative(objName, funcName, payload); if (!invokeSuccess) { delete window.__bridge_callbacks__[payload.__callbackId__]; reject(new Error('Native Bridge 环境不存在')); } }); }, /** * 原生统一回调入口,由App主动执行 * @param {string} callbackId 异步请求唯一标识 * @param {string|object} resultJson 原生返回数据 */ onCallback(callbackId, resultJson) { const cbCache = window.__bridge_callbacks__?.[callbackId]; if (!cbCache) return; // 统一格式化返回数据 const result = typeof resultJson === 'string' ? JSON.parse(resultJson) : resultJson; // 约定协议:code=0 成功,其余失败 if (result.code === 0 || result.success) { cbCache.resolve(result.data); } else { cbCache.reject(result); } // 销毁缓存,防止内存泄漏 delete window.__bridge_callbacks__[callbackId]; }, /** 获取当前系统标识 */ getOS: detectOS }; })(); // 兼容打包工具导出 if (typeof module !== 'undefined' && module.exports) { module.exports = Bridge; }

五、业务层实战调用示例(main.js)

import Bridge from './bridge.js'; // 同步单向调用(仅通知原生,不需要返回) function showNativeToast() { Bridge.invoke('NativeBridge', 'showToast', { message: '操作成功', duration: 2000 }); } // Promise 异步调用,等待原生返回数据 async function fetchUserInfo() { try { const user = await Bridge.invokeAsync( 'NativeBridge', 'getUserInfo', {}, 'cb_get_user' ); console.log('获取用户信息成功:', user); } catch (err) { console.error('获取用户信息失败:', err); } } // 平台差异化UI适配 function adaptSafeArea() { if (Bridge.getOS() === 'ios') { // iPhone底部小黑条安全区适配 document.body.style.paddingBottom = '34px'; } } // 页面初始化执行 document.addEventListener('DOMContentLoaded', () => { adaptSafeArea(); showNativeToast(); fetchUserInfo(); });

原生主动回调 H5 执行语句

App 处理完业务后,执行下面 JS 将结果回传给页面 Promise:

// Android / iOS 统一执行脚本示例 window.Bridge.onCallback("cb_get_user", '{"code":0,"data":{"name":"张三","userId":10001}}')

六、生产环境避坑注意事项

  1. Android 安全强制注解所有暴露给 JS 的方法必须添加@JavascriptInterface,Android 4.2+ 无注解会直接拦截调用,出现无响应。
  2. 数据格式兼容问题Android 仅支持字符串参数,H5 调用原生方法必须JSON.stringify;iOS postMessage 可直接传对象,但为双端统一建议全部序列化。
  3. 回调内存泄漏异步请求回调执行完成后必须删除全局缓存的 resolve/reject,页面频繁刷新、多次请求会造成内存堆积。
  4. 命名强约定H5 与原生的桥对象名、方法名、回调字段__callbackId__必须完全一致,大小写敏感。
  5. WebView 生命周期页面销毁前清空全局window.__bridge_callbacks__,避免页面卸载后原生回调空白页面报错。
  6. iOS 废弃 UIWebViewUIWebView 无webkit.messageHandlers通道,必须使用 WKWebView。

七、全文总结

  1. 原生调 H5:依靠 WebView 执行 JS,调用 window 全局挂载的回调函数;
  2. H5 调原生:Android 对象注入、iOS WKWebView 消息通道,两套底层 API 完全隔离;
  3. 工程化方案:通过 JSBridge 闭包封装屏蔽平台差异,对外提供统一invoke/invokeAsync
  4. 异步闭环:CallbackId 映射 Promise 缓存,原生通过统一onCallback分发结果,替代传统多层回调;
  5. 落地价值:一套 JS 代码同时兼容双端,业务层无需写大量平台判断,可直接投入线上生产。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 10:55:13

Linux 【05- scp命令超详细教程】

Linux scp 命令超详细完整教程 一、scp 基础介绍 1. 作用 scp secure copy&#xff0c;基于 SSH 加密传输文件&#xff0c;跨服务器复制文件/文件夹&#xff0c;传输全程加密&#xff0c;比 rcp、ftp 安全。 2. 前置条件 两台机器都安装 openssh-client / openssh-server目标机…

作者头像 李华
网站建设 2026/7/2 10:50:56

Sunshine游戏串流终极指南:三步打造你的私人云游戏服务器

Sunshine游戏串流终极指南&#xff1a;三步打造你的私人云游戏服务器 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 你是否厌倦了被云游戏服务商的订阅费用束缚&#xff1f;是否想…

作者头像 李华
网站建设 2026/7/2 10:46:59

claude code 开发实践 - 生产级别的项目规范

Claude Code 开发规范博主是某个技术团队的负责人&#xff0c;团队规模中等&#xff08;50人&#xff09;&#xff0c;今年开始团队成员应用AI辅助编程的越来越多&#xff0c;效率提升的同时也逐渐暴露出很多问题&#xff1a; AI 生成代码出现架构不统一、编码规范混乱、业务逻…

作者头像 李华
网站建设 2026/7/2 10:46:39

东芝TC78H653FTG与PIC18LF46K22的直流电机驱动方案

1. 项目背景与核心器件解析在工业自动化和消费电子领域&#xff0c;直流有刷电机因其结构简单、控制方便且成本低廉的特点&#xff0c;始终占据着重要地位。然而&#xff0c;传统驱动方案往往存在效率低下、控制精度不足等问题。东芝推出的TC78H653FTG H桥驱动器与Microchip的P…

作者头像 李华
网站建设 2026/7/2 10:46:21

科普漫画:散热器的临终独白:我不是被热死的,我是被闷死的

&#xfffd;&#xfffd; “我不是被热死的&#xff0c;我是被闷死的。”这是一块GPU散热器留在返修台上的最后一句话。所有人都以为它是被700W的热量烧坏的——直到拆开才发现&#xff1a;翅片之间塞满了厚厚的灰尘&#xff0c;像给它穿了一件不透气的棉袄。风进不去&#xf…

作者头像 李华