ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ArkWeb 手记 02|让 ArkTS 和 H5 真正通信

2026/9/30 11:31:04 拓冰建站 浏览量
ArkWeb 手记 02|让 ArkTS 和 H5 真正通信 HarmonyOS 7 · ArkWeb 混合应用开发手记 02上一篇把 Web页面的加载、失败重试和生命周期理顺了。这一篇继续往下走页面能打开以后ArkTS和 H5 到底怎么说话。做混合应用真正开始麻烦的往往不是把网页打开。页面上线没两天需求就来了H5 点分享按钮要调原生分享H5登录成功要把结果通知 ArkTS原生拿到 Token要交给H5支付完成以后还得把支付结果推回网页。如果这些逻辑全靠 URL 参数、刷新页面或者到处runJavaScript()项目很快就会乱。所以这一篇不堆 API。我们直接做一条完整链路H5 → JSBridge → ArkTS再从 ArkTS → H5。1. 先把通信关系想简单一点我更愿意把 JSBridge 理解成前台。H5 不需要知道 ArkTS 后面到底调用了哪个Kit也不应该直接关心原生实现。它只认识一个固定对象比如NativeBridge.showToast(保存成功)ArkTS 侧负责真正执行showToast(message:string):void{console.info([Bridge] showToast:${message})}反过来ArkTS 想通知 H5也不要去改 H5 的内部变量。给网页约定一个入口window.onNativeMessagefunction(data){console.log(收到原生消息,data)}然后 ArkTS 调这个入口。整个关系其实就三层H5 页面 ↓ NativeBridge ↓ ArkTS 业务能力反方向也是一样。2. 第一步先准备一个最小 Bridge先别上来就封装几十个方法。我们只做两个能力打印 H5 发来的消息以及给 H5 返回一个演示 Token。classJsBridge{showToast(message:string):void{console.info([JsBridge] showToast:${message})}getToken():string{returndemo-token-123}}然后在页面里创建对象import{webview}fromkit.ArkWebEntryComponentstruct WebBridgePage{privatecontroller:webview.WebviewControllernewwebview.WebviewController()privatebridge:JsBridgenewJsBridge()build(){Column(){// Web 放这里}}}这里有个小细节。bridge最好是页面实例上的稳定对象不要在各种回调里反复new JsBridge()。原因很简单你后面还会给它加登录、分享、支付等方法。如果对象生命周期都不稳定排查H5为什么突然找不到方法会非常难受。3. 把 ArkTS 方法暴露给 H5接下来才是关键让网页能看到这个对象。Web({src:this.url,controller:this.controller}).javaScriptAccess(true).javaScriptProxy({object:this.bridge,name:NativeBridge,methodList:[showToast,getToken],controller:this.controller})这里最值得记的是三个东西。object是实际的 ArkTS 对象。name是 H5 看到的对象名。methodList是你明确允许 H5 调用的方法。也就是说name:NativeBridge决定了网页里写NativeBridge.showToast(...)而不是JsBridge.showToast(...)methodList也不要图省事什么都往里塞。Bridge 本质上是 Web页面通向原生能力的一扇门。门开多大应该由原生侧决定。例如methodList:[showToast,getToken]这比做一个万能的execute(method:string,params:string)更容易看懂也更容易限制能力范围。4. H5 侧真正调用一次为了验证链路H5 可以先写得非常简单!DOCTYPEhtmlhtmlheadmetacharsetutf-8titleArkWeb Bridge Demo/title/headbodybuttononclickcallNative()调用 ArkTS/buttonbuttononclickreadToken()获取 Token/buttonscriptfunctioncallNative(){if(!window.NativeBridge){console.error(NativeBridge 还不可用)return}NativeBridge.showToast(H5 已经连上 ArkTS)}functionreadToken(){if(!window.NativeBridge){return}consttokenNativeBridge.getToken()console.log(token ,token)}/script/body/html这里我故意保留了if(!window.NativeBridge)Demo 里可能觉得多余。实际项目里非常有用。因为同一套 H5 往往还要跑浏览器环境。浏览器里没有 ArkWeb 注入的NativeBridge如果不判断页面一点击就直接报错。更好的写法甚至可以再包一层functionisInApp(){return!!window.NativeBridge}以后页面要兼容浏览器和 HarmonyOS App就不用到处写判断。5. 最容易踩坑的其实是什么时候能调上一篇我们一直在讲生命周期。到了 JSBridge这个问题更明显。很多人看到privatebridgenewJsBridge()就认为 Bridge 已经 Ready。不是。ArkTS 对象创建好了只代表对象存在。Web 控制器挂载、页面加载、代理对象对 H5 可见、H5自己的业务脚本初始化这些节点不是同一个时间点。我一般把它拆成Web 创建 ↓ Controller Attached ↓ 页面开始加载 ↓ Bridge 可被页面使用 ↓ H5 自己初始化完成 ↓ 业务通信所以 H5 最好也有一个 Ready 概念。例如window.bridgeReadyfunction(){console.log(Bridge Ready)}或者更实际一点H5 初始化时主动探测functioncheckBridge(){if(window.NativeBridge){document.body.dataset.bridgereadyreturn}setTimeout(checkBridge,100)}checkBridge()当然不建议无限轮询。正式项目更适合把原生 Ready和H5 Ready设计成明确握手协议。第 03篇我们会专门做这件事。6. 参数别一开始就玩得太花简单参数很好处理showToast(message:string):void{console.info(message)}H5NativeBridge.showToast(保存成功)但业务很快就会变成对象{title:来自 H5 的分享,url:https://example.com/detail/1001,scene:detail}这时候我更建议第一版协议直接使用 JSON 字符串。ArkTSopenShare(params:string):void{try{constdata:Recordstring,stringJSON.parse(params)console.info([Share] title${data.title})}catch(error){console.error([Share] 参数解析失败)}}H5constparams{title:来自 H5 的分享,url:location.href,scene:detail}NativeBridge.openShare(JSON.stringify(params))为什么宁可多一次JSON.stringify()因为协议边界更清楚。你抓日志时能直接看到完整原始参数版本变化也比较容易兼容。不要为了少写几行代码让 Bridge 方法同时接收五六个位置参数openShare(title,url,image,scene,source,...)这种接口需求改两次就开始痛苦。7. Bridge 方法里一定要自己兜错误H5 调原生不代表 H5 传过来的东西永远靠谱。例如openShare(params:string):void{constdataJSON.parse(params)}参数只要不是合法 JSON就可能直接出问题。至少做一层保护openShare(params:string):void{try{constdata:Recordstring,stringJSON.parse(params)if(!data.url){console.error([Bridge] share url is empty)return}console.info([Bridge] openShare:${data.url})}catch(error){console.error([Bridge] invalid params:${params})}}Bridge 是边界层。边界层最重要的工作之一就是不要盲信另一边传来的数据。这件事后面做到登录、支付、文件能力时会更重要。8. 反过来ArkTS 怎么主动通知 H5到这里我们只是做通了H5 → ArkTS真实业务一定还需要ArkTS → H5比如原生支付结束{type:payResult,success:true,orderId:A10001}我们先让 H5 定义统一入口scriptwindow.onNativeMessagefunction(data){console.log(收到 ArkTS 消息,data)if(data.typepayResult){document.getElementById(result).innerTextdata.success?支付成功:支付失败}}/scriptArkTS 侧再执行 JavaScriptprivatesendMessageToH5(data:object):void{constjsonJSON.stringify(data)constscriptwindow.onNativeMessage window.onNativeMessage(${json})this.controller.runJavaScript(script)}调用this.sendMessageToH5({type:payResult,success:true,orderId:A10001})这里有两个重点。第一不要假设 H5 一定定义了函数所以先判断window.onNativeMessage第二传对象时不要自己手拼 JSON。直接JSON.stringify(data)手拼字符串非常容易在引号、换行和特殊字符上翻车。9.runJavaScript()能用但别用成万能 Bridge既然runJavaScript()可以执行网页里的 JS有人会问那我还注册javaScriptProxy干嘛全部runJavaScript()不就好了技术上你能写很多。工程上不建议。我通常这样分H5 主动请求原生能力 ↓ JavaScriptProxy / Bridge ArkTS 主动通知 H5 ↓ runJavaScript / 约定好的 H5 接口这样方向非常明确。例如 H5 想获取 TokenNativeBridge.getToken()原生支付结束通知 H5this.sendMessageToH5({type:payResult,success:true})以后看到代码基本不用猜是谁主动发起的。如果所有通信都变成拼 JavaScript 字符串半年以后你搜一个方法名会发现ArkTS、H5、日志、字符串模板里到处都是。那种项目排查起来真的很折磨。10. 再往前一步给消息加type当 ArkTS 只通知一个事件时window.onPaySuccess()看起来很舒服。等业务多起来以后window.onPaySuccess()window.onLoginExpired()window.onThemeChanged()window.onLocationChanged()window.onRefreshUser()H5 全局对象很快就塞满了。所以我更喜欢一个入口window.onNativeMessage(data)所有消息靠type区分{type:loginSuccess,data:{userId:1001}}或者{type:payResult,data:{success:true}}H5 统一分发window.onNativeMessagefunction(message){switch(message.type){caseloginSuccess:handleLogin(message.data)breakcasepayResult:handlePayResult(message.data)breakdefault:console.warn(未知原生消息,message)}}这已经有点通信协议的味道了。没错。混合应用做到后面JSBridge真正难的从来不是能不能调用而是双方怎么约定一套不会越来越乱的协议。这也是下一篇要继续解决的问题。11. 日志一定要把方向打印出来调 Bridge 时我特别建议日志直接带方向。例如 ArkTS 收到 H5console.info([Bridge][H5-ArkTS] showToast:${message})ArkTS 发给 H5console.info([Bridge][ArkTS-H5]${JSON.stringify(data)})这样控制台一眼就能看出来[Bridge][H5-ArkTS] getToken [Bridge][H5-ArkTS] openShare [Bridge][ArkTS-H5] {type:payResult,success:true}如果只打印success message callback done过几天连你自己都不知道是谁打的。12. 这一版先别急着做成大而全到这里我们已经能完成一条完整通信H5 ↓ NativeBridge.showToast() ↓ ArkTS ArkTS ↓ controller.runJavaScript() ↓ window.onNativeMessage() ↓ H5这时候最容易犯的错误是马上写一个几百行的BridgeManager。先别急。先保证三件事第一调用方向清楚。H5 请求原生能力走 BridgeArkTS 主动通知 H5 走统一消息入口。第二参数格式稳定。简单值直接传复杂业务对象尽量统一 JSON 协议。第三时序可判断。不要把controller 创建了“页面加载了”“Bridge 可用了”H5 业务 ready了混成一件事。这三件事稳定以后再封装才有意义。小结这一篇我们真正打通了 ArkWeb 混合应用里最基础的一条路。H5 可以通过javaScriptProxy暴露出来的对象调用 ArkTS。ArkTS 可以通过WebviewController执行约定好的 H5方法把消息再送回网页。看起来代码不多但这里已经出现了几个后面必须解决的问题Bridge 方法越来越多怎么办异步方法怎么返回结果同一个页面同时发多个请求怎么知道回调对应谁Bridge 没 Ready 时消息要不要排队H5 和 ArkTS 版本不一致怎么办这些问题如果继续靠一个个方法硬写第三篇就会开始失控。所以下一篇我们不继续加业务能力而是先把基础设施补上ArkWeb 手记 03把 JSBridge 封装成一套协议。会把requestId、Promise、统一返回结构、错误码、超时和消息分发真正串起来。留一个小练习在这一篇的 Demo 上再增加一个NativeBridge.getAppInfo()让 ArkTS 返回应用版本和系统信息然后让 H5 把结果显示在页面上。要求只有一个不要增加新的全局回调函数。试着全部走window.onNativeMessage()。做到这里基本就已经理解为什么下一篇需要统一协议了。