JSBridge:客户端与H5的通信桥梁

张开发
2026/4/7 23:25:47 15 分钟阅读

分享文章

JSBridge:客户端与H5的通信桥梁
JSBridge的定义App中的H5页面是运行在webview的沙箱中无法直接调用原生APP的能力比如获取APP登录的用户信息、获取设备信息、调用支付、拍照、分享、扫码等原生APP也无法直接通知H5页面登录状态变化、网络变化、音量/亮度变化等。为了解决这个问题目前主流方案有三种WebView 接口注入原生向 WebView 注入 JS 对象H5 直接调用URL Scheme 拦截伪协议H5 发起myapp://action?paramsWebView 拦截并解析参数长度受限需手动解析性能略差JavaScript 代码执行evaluateJavascript原生主动执行 H5 中已定义的 JS 函数只能单向从 APP 到 H5无法直接获取返回值需配合回调。实际生产环境中混合使用方案 1 方案 3最常见H5 → APP使用**JSBridge 对象注入**APP → H5使用**evaluateJavascript**执行 H5 的全局回调函数这种可以解决两者的通信问题一般可以统称为JSBridge。接下来概述一下客户端与H5中该如何接收/派送消息。常见的营销活动页需求由于营销活动页每隔一段时间就会更新一次且会被多端应用原生APP、小程序等等使用所以一般营销活动页面会做成H5的模式。若用户打开APP从主页点开营销活动页面那么进入该页面的时候就需要进行判断用户已经登录则调用APP获取用户信息H5调用App获取用户信息用户未登录则页面显示登录按钮点击跳转到原生APP登录页登录成功之后登录页再返回H5页面登录成功后App通知H5。但是原生APP与H5并不能直接进行对话。接下来以这个为前提介绍一下通信问题的解决方式。JSBridge 对象注入从App主页点击进h5页面时发现用户未登录那就会显示一个登录按钮点击之后就应该跳转到原生登录页上。现在我们先需要app向webview全局代码注入一个登录方法// 以安卓代码为例子publicclassWebViewActivityextendsAppCompatActivity{privateWebViewwebView;// 当前webview内嵌某个h5页面privateStringh5Urlhttp://localhost:5500/index.html;// 用于接收登录页返回的结果privateActivityResultLauncherIntentloginLauncher;// 将要想要注入到webview的全局方法封装称对象。// 这样h5页面中点击就可以调用这个login从当前的webview跳转到原生登录页privateclassJSBridge{JavascriptInterfacepublicvoidlogin(Stringparams){// H5 调用此方法在这里打开原生登录页IntentintentnewIntent(WebViewActivity.this,LoginActivity.class);startActivity(intent);}}// 在生命周期创建的时候一个webviewSuppressLint(SetJavaScriptEnabled)OverrideprotectedvoidonCreate(BundlesavedInstanceState){// ...// 创建webview相当于挂载在某个idwebView的元素上webViewfindViewById(R.id.webView);WebSettingssettingswebView.getSettings();// 当前webview支持使用jssettings.setJavaScriptEnabled(true);// 支持WebView 中的网页就可以使用localStorage和sessionStoragewebView.getSettings().setDomStorageEnabled(true);// 给当前定义一个标识有利于h5可以通过判断ua处于什么样的环境app小程序h5// 修改 User-Agent添加自定义标识 MyApp这样 H5 端的 navigator.userAgent 就会包含 MyAppStringuasettings.getUserAgentString();settings.setUserAgentString(ua MyApp/1.0);// 让 WebView 在自身打开链接不要调起系统浏览器webView.setWebViewClient(newWebViewClient());// ❗️❗️❗️重点向webview 注入 JSBridge 对象名字叫 bridge 上面封装的那个webView.addJavascriptInterface(newJSBridge(),bridge);// 加载 H5 页面webView.loadUrl(h5Url);// ....}}由于是在app中点击登录按钮那么h5页面需要使用全局挂载的window.bridge.login();方法。// 判断是否是从app打开的h5页面functionisInApp(){// 如果有 JSBridge 对象App 注入则认为在 App 内if(window.bridgetypeofwindow.bridge.callfunction){returntrue;}// 或者检测自定义 UA 标识例如 MyApp/1.0constuanavigator.userAgent.toLowerCase();// 请将 myapp 替换为你 App 的 UA 关键字if(ua.includes(myapp)||ua.includes(yourappname)){returntrue;}returnfalse;}// 当未登录的时候就调用这个方法functionshowNotLoggedIn(){// 先判断当前环境是不是出于app有时候也需要考虑小程序的情况constinAppisInApp();// 如果是appif(inApp){// 就在页面渲染一个登录按钮按钮绑定函数,调用全局的window.bridge.login函数contentDiv.innerHTMLbutton classlogin-btn idappLoginBtn登录/button;constloginBtndocument.getElementById(appLoginBtn);if(loginBtn){loginBtn.addEventListener(click,(){// ❗️❗️❗️调用原生 JSBridgeif(window.bridgewindow.bridge.login){window.bridge.login();// 参数可传空}else{alert(JSBridge 未就绪);}});}return;}// ...}JavaScript 代码执行接下来原生app在登录页面完成登录之后需要通知h5页面告诉它已经登录成功你可以用我传给你的token保存起来并在当前页面获取用户信息。所以我们需要先在h5页面做一个监听的动作// ---------- ❗️❗️❗️接收原生登录成功回调 ----------window.onLoginSuccessfunction(token){console.log([H5] 收到原生登录回调token:,token);if(token){localStorage.setItem(access_token,token);// 重新检查登录状态刷新页面内容checkLoginStatus();}else{console.error([H5] token 无效);}};在app中如何通知h5可以借助webView.evaluateJavascript(js, null);。// 1. 先修改app中注册的login方法希望用户关闭login的时候可以触发某些逻辑即将login中获得的token结果通知h5页面。先修改方法privateclassJSBridge{JavascriptInterfacepublicvoidlogin(Stringparams){// 打开原生登录页面IntentintentnewIntent(WebViewActivity.this,LoginActivity.class);// startActivity(intent);// 前面我们声明了一个这样的变量loginLauncherloginLauncher.launch(intent);}}// 在onCreate添加逻辑protectedvoidonCreate(BundlesavedInstanceState){// ....webView.loadUrl(h5Url);// 注册一个启动登录页的 launcher当登录页面被关闭时用于接收登录结果loginLauncherregisterForActivityResult(newActivityResultContracts.StartActivityForResult(),result-{if(result.getResultCode()RESULT_OKresult.getData()!null){Stringtokenresult.getData().getStringExtra(access_token);if(token!null){// ❗️❗️❗️登录成功将 token 传给 H5即发送消息给h5Stringjsjavascript:window.onLoginSuccess(token);webView.evaluateJavascript(js,null);}}});}本地测试可能遇到的问题我使用的是原生html写的h5页面作为测试页面在vscode使用live server插件启动页面所以页面的访问地址是http://localhost:5500/index.html。但是Android 模拟器无法访问你电脑上的 Live Server所以需要做一些配置// 挂载页面时使用地址privateString h5Urlhttp://localhost:5500/index.html;// ...// 加载 H5 页面webView.loadUrl(h5Url);然后确保你的live server已经启动接着使用adb端口转发adb reverse tcp:5500 tcp:5500同理由于我的h5调用的接口是本地地址http://localhost:3000/所以也需要做转发bash adb reverse tcp:3000 tcp:3000h5开发需要做一些调试的时候最好在页面装一个vConsole / Eruda。我本来使用的cdn内联但是本地调试会加载不出来所以最后是将文件下载下来使用相对路径引入scriptsrc./eruda.js/scriptscripteruda.init();/script封装一个通用的JSBridge// bridge.jsclassJSBridge{constructor(){this.callbackMap{};// 存储回调函数 { callbackId: function }this.callbackId0;// 自增 IDthis.isIOSfalse;this.isAndroidfalse;this._initPlatform();this._registerNativeCallHandler();}// 检测平台_initPlatform(){// iOS WKWebView 特有属性if(window.webkitwindow.webkit.messageHandlers){this.isIOStrue;}// Android 通过 addJavascriptInterface 注入的对象elseif(window.JSBridge){this.isAndroidtrue;}else{console.warn(JSBridge: 未检测到原生环境将使用模拟模式);}}// 注册一个全局函数供原生主动调用 H5_registerNativeCallHandler(){window._handleNativeCall(callbackId,method,params){// 原生调用 H5 注册的方法例如原生通知登录成功if(this._nativeHandlers[method]){constresultthis._nativeHandlers[method](params);// 如果需要回调给原生可以通过 callbackId 返回if(callbackId){this._sendToNative(_nativeCallback,{callbackId,result});}}};}// 存储 H5 注册的供原生调用的方法_nativeHandlers{};// H5 注册一个方法让原生可以调用例如 onLoginSuccessregister(method,handler){this._nativeHandlers[method]handler;}// 发送消息给原生核心方法_sendToNative(action,data){constmessage{action,data};if(this.isIOS){// iOS: 通过 messageHandlers 发送window.webkit.messageHandlers.JSBridge.postMessage(message);}elseif(this.isAndroid){// Android: 直接调用注入对象的方法window.JSBridge.handleMessage(JSON.stringify(message));}else{// 模拟环境打印日志console.log([JSBridge Mock],action,data);}}// H5 调用原生能力支持 callback 和 Promisecall(method,params{},callbacknull){if(callback){// 使用 callback 方式constcallbackIdthis._generateCallbackId();this.callbackMap[callbackId]callback;this._sendToNative(method,{params,callbackId});}else{// 返回 PromisereturnnewPromise((resolve,reject){constcallbackIdthis._generateCallbackId();this.callbackMap[callbackId](err,result){if(err)reject(err);elseresolve(result);};this._sendToNative(method,{params,callbackId});});}}// 生成唯一回调 ID_generateCallbackId(){returncb_${Date.now()}_${this.callbackId};}// 原生调用此方法来回调 H5原生必须执行这个 JS 函数_receiveCallback(callbackId,err,result){if(this.callbackMap[callbackId]){constcbthis.callbackMap[callbackId];deletethis.callbackMap[callbackId];cb(err,result);}}}// 挂载到 window全局单例window.bridgenewJSBridge();// 暴露一个原生可以调用的回调接收函数window._bridgeCallback(callbackId,err,result){window.bridge._receiveCallback(callbackId,err,result);};使用方式如下// H5调用原生方法// 调用原生 Toast不需要回调window.bridge.call(toast,{msg:Hello from H5});// 调用原生获取设备信息使用 callbackwindow.bridge.call(getDeviceInfo,{},(err,result){if(err)console.error(err);elseconsole.log(设备信息,result);});// 使用 Promisewindow.bridge.call(getDeviceInfo,{}).then(resultconsole.log(result)).catch(errconsole.error(err));// 注册一个供原生调用的方法// 原生可以执行 window._handleNativeCall(, onLoginSuccess, { userId: 123 })window.bridge.register(onLoginSuccess,(params){console.log(原生登录成功,params);// 可以更新页面、跳转等document.getElementById(user).innerTextparams.userId;});小结了解JSBridge是一种解决原生APP与H5之间进行通信的方法统称H5-APP需要APP在创建webview的时候注入一个全局对象对象上面挂在了多个方法H5可以直接通过全局对象调用内部方法从而达到调用/通知APP的目的APP-H5需要H5在全局注册好监听事件而APP通过evaluateJavaScript执行方法进而达到与H5通信的目的。

更多文章