尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

H5 调 ArkTS、ArkTS 再回调 H5:ArkWeb 双向通信完整实践【鸿蒙心迹】

发布时间:2026/9/29 9:51:01

资讯中心
01
ARTICLE

H5 调 ArkTS、ArkTS 再回调 H5:ArkWeb 双向通信完整实践【鸿蒙心迹】

H5 调 ArkTS、ArkTS 再回调 H5:ArkWeb 双向通信完整实践【鸿蒙心迹】
大家好我是[晚风依旧似温柔]新人一枚欢迎大家关注~本文目录前言一、为什么混合应用需要 JS Bridge二、先确认版本和能力边界三、先搭一个最小实践四、先实现 H5 调 ArkTS1. 定义统一请求结构2. ArkTS 侧 Bridge五、把 Bridge 注入 Web 页面六、H5 侧把调用包装成 Promise七、ArkTS 调 H5为什么不能只靠字符串拼接八、返回结果和异步调用怎么设计九、不可信网页为什么不能随意暴露 Native 方法十、几个容易理解错的地方1. runJavaScript() 不等于“调用固定 H5 API”2. Bridge 可用时机和页面加载时机不是一回事3. 对象参数最好建立自己的协议4. Bridge 不再使用时要解除注册十一、实际项目中怎么排查十二、什么时候改用 WebMessagePort开发经验总结前言混合应用里有一类很典型的需求页面主体由 H5 实现但登录态、设备能力、系统页面跳转等能力掌握在应用侧。H5 需要调用 ArkTSArkTS 处理完成后又要把结果送回网页。如果只是单向通知这件事并不复杂。真正容易把代码写乱的是“H5 发起请求 → ArkTS 接收参数 → 执行业务 → ArkTS 回调 H5 → H5 恢复对应 Promise”这一整条链路。这次用一个本地 H5 最小示例把 ArkWeb 中这条双向通信链路完整串起来并进一步封装成一个统一 Bridge。一、为什么混合应用需要 JS Bridge先看一个具体场景。假设应用中有一个活动页由 H5 开发。页面需要两个能力H5 传入两个数字让 ArkTS 完成计算并立即返回H5 发起一个异步请求ArkTS 处理完成后主动回调 H5。这两个需求对应两条方向相反的通道H5 - ArkTS JavaScriptProxy / registerJavaScriptProxy ArkTS - H5 WebviewController.runJavaScript()华为 ArkWeb 官方开发指南把“应用侧调用前端页面函数”“前端页面调用应用侧函数”和“建立应用侧与前端页面数据通道”分别作为 Web 与 JavaScript 交互能力进行说明。对于持续的消息型通信官方还提供createWebMessagePorts()创建消息端口。本文不做消息端口方案而是聚焦更接近传统 JS Bridge 的调用模型H5 │ │ window.NativeBridge.invoke(...) ▼ ArkTS │ │ runJavaScript(...) ▼ H5 callback这样做的好处是业务层可以继续使用“方法名 参数 Promise”的调用习惯而不用让每个 H5 页面自己拼 ArkWeb API。二、先确认版本和能力边界本文以HarmonyOS 7、API 26、Stage 模型应用作为目标开发背景。华为官方已经明确HarmonyOS 7.0 对应 API 26.0.0从 26.0.0 开始HarmonyOS 开发套件的 API 版本号改用X.Y.Z语义化版本格式。官方同时建议面向 HarmonyOS 7 的应用使用 26.0.0 开发套件进行升级适配。这里使用的核心模块是import{webview}fromkit.ArkWeb;核心对象是webview.WebviewController本文涉及的主要能力包括能力用途Web在 ArkUI 页面中承载 H5$rawfile()引用应用包内的本地网页资源javaScriptProxy()Web 初始化时把 ArkTS 对象注册到网页环境runJavaScript()应用侧执行当前网页上下文中的 JavaScriptdeleteJavaScriptRegister()删除已经注册的 JavaScriptProxy 对象runJavaScript()是异步执行接口执行结果通过 Promise 返回官方 FAQ 中也明确说明它在当前显示页面上下文执行 JavaScript并要求在 UI 线程使用。还有一个边界必须单独说明**本文讨论的是 HarmonyOS 应用中的 ArkWebWeb组件不是元服务的AtomicServiceEnhancedWeb。**截至 2026 年 9 月的官方 FAQAtomicServiceEnhancedWeb暂不支持runJavaScript()和registerJavaScriptProxy()不能把本文代码直接套到该组件上。这个区别很容易被忽略。三、先搭一个最小实践工程中准备一个本地网页entry └── src └── main ├── ets │ └── pages │ └── Index.ets └── resources └── rawfile └── bridge └── index.html本文只加载应用包中的$rawfile()本地页面把网络请求、登录、路由等业务全部拿掉。最终希望实现两个调用HarmonyBridge.call(sum,{a:10,b:20});立即得到30再调用HarmonyBridge.call(delayEcho,{message:Hello ArkTS});ArkTS 先接收请求异步处理结束后再主动执行 H5 中的回调函数让这个调用最终仍然表现为一个 Promise。四、先实现 H5 调 ArkTS1. 定义统一请求结构如果每增加一个能力就往window上暴露一个方法getUser() openPage() scan() pay() getLocation() ...Bridge 很快就会失去边界。更容易维护的办法是只暴露一个入口NativeBridge.invoke(requestJson)请求统一成{id:req_001,method:sum,params:{\a\:10,\b\:20}}id用于异步结果匹配method表示要调用的能力params保存业务参数。2. ArkTS 侧 Bridge下面代码按照 ArkWeb 官方 JavaScriptProxy 和runJavaScript()的接口形式组织为最小示例。由于这里没有实际执行 HarmonyOS 工程编译发布前仍应使用目标 API 26 SDK 做一次工程级校验。import{webview}fromkit.ArkWeb;import{BusinessError}fromkit.BasicServicesKit;interfaceBridgeRequest{id:string;method:string;params:string;}interfaceBridgeResponse{id:string;ok:boolean;pending:boolean;data:string;error:string;}interfaceSumParams{a:number;b:number;}interfaceEchoParams{message:string;}constwebController:webview.WebviewControllernewwebview.WebviewController();functioncreateResponse(id:string,ok:boolean,pending:boolean,data:string,error:string):BridgeResponse{return{id,ok,pending,data,error};}functioncallbackToH5(response:BridgeResponse):void{constresponseJsonJSON.stringify(response);// 再 stringify 一次把 JSON 文本安全地变成 JS 字符串字面量。constscriptwindow.HarmonyBridge.__resolve(${JSON.stringify(responseJson)});webController.runJavaScript(script).catch((error:BusinessError){console.error(runJavaScript failed, code${error.code}, message${error.message});});}classNativeBridge{invoke(requestJson:string):string{try{constrequestJSON.parse(requestJson)asBridgeRequest;switch(request.method){casesum:{constparamsJSON.parse(request.params)asSumParams;constresultparams.aparams.b;returnJSON.stringify(createResponse(request.id,true,false,result.toString(),));}casedelayEcho:{constparamsJSON.parse(request.params)asEchoParams;setTimeout((){callbackToH5(createResponse(request.id,true,false,ArkTS received:${params.message},));},500);returnJSON.stringify(createResponse(request.id,true,true,,));}default:returnJSON.stringify(createResponse(request.id,false,false,,Unknown method:${request.method}));}}catch(error){returnJSON.stringify(createResponse(,false,false,,Invalid bridge request:${String(error)}));}}}constnativeBridgenewNativeBridge();这里真正需要关注的不是switch而是协议。同步调用直接返回完整BridgeResponse异步调用先返回{pending:true}等业务结束后再通过runJavaScript()把最终结果推给网页。这样同步和异步能力可以共用一个入口。五、把 Bridge 注入 Web 页面页面部分保持很小EntryComponentstruct Index{aboutToDisappear():void{try{webController.deleteJavaScriptRegister(NativeBridge);}catch(error){consterrerrorasBusinessError;console.error(deleteJavaScriptRegister failed:${err.code},${err.message});}}build(){Column(){Web({src:$rawfile(bridge/index.html),controller:webController}).width(100%).height(100%).javaScriptAccess(true).javaScriptProxy({object:nativeBridge,name:NativeBridge,methodList:[invoke],asyncMethodList:[],controller:webController,permission:{javascriptProxyPermission:{urlPermissionList:[{scheme:resource,host:rawfile,port:,path:}]}}});}.width(100%).height(100%);}}这里没有把几十个 Native 方法直接暴露出去只注册NativeBridge.invoke同时给 JavaScriptProxy 配置了 URL 权限范围只允许resource://rawfile这一类本地资源来源调用 Bridge。官方 JavaScriptProxy 示例同样提供了javascriptProxyPermission、urlPermissionList以及 scheme、host、port、path 等粒度的限制方式在官方 H5 适配指导中也可以看到registerJavaScriptProxy()配合 URL 权限范围的用法。六、H5 侧把调用包装成 Promise接下来处理网页。!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0titleArkWeb Bridge Demo/title/headbodybuttononclicktestSum()同步调用/buttonbuttononclicktestAsync()异步调用/buttonpreidresult/prescriptconstpendingCallsnewMap();letrequestSeed0;window.HarmonyBridge{call(method,params{}){returnnewPromise((resolve,reject){if(!window.NativeBridge||typeofwindow.NativeBridge.invoke!function){reject(newError(NativeBridge is not available));return;}constidreq_${Date.now()}_${requestSeed};pendingCalls.set(id,{resolve,reject});constrequest{id,method,params:JSON.stringify(params)};try{constrawwindow.NativeBridge.invoke(JSON.stringify(request));constresponseJSON.parse(raw);// pendingtrue 表示 ArkTS 稍后主动回调。if(response.pending){return;}pendingCalls.delete(id);if(response.ok){resolve(response.data);}else{reject(newError(response.error));}}catch(error){pendingCalls.delete(id);reject(error);}});},__resolve(responseJson){constresponseJSON.parse(responseJson);constpendingpendingCalls.get(response.id);if(!pending){return;}pendingCalls.delete(response.id);if(response.ok){pending.resolve(response.data);}else{pending.reject(newError(response.error));}}};asyncfunctiontestSum(){try{constresultawaitHarmonyBridge.call(sum,{a:10,b:20});document.getElementById(result).textContentsum result:${result};}catch(error){document.getElementById(result).textContentString(error);}}asyncfunctiontestAsync(){try{constresultawaitHarmonyBridge.call(delayEcho,{message:Hello ArkTS});document.getElementById(result).textContentresult;}catch(error){document.getElementById(result).textContentString(error);}}/script/body/html到这里H5 已经不需要知道runJavaScript()、WebviewController或 ArkTS 类是什么。业务页面只认识awaitHarmonyBridge.call(method,params);这正是统一 Bridge 最有价值的地方平台通信细节被压到桥接层业务代码只处理方法、参数和结果。七、ArkTS 调 H5为什么不能只靠字符串拼接ArkTS 回调网页的关键代码是webController.runJavaScript(script);官方说明中runJavaScript()会在当前页面上下文异步执行 JavaScript如果需要获得更丰富的 JavaScript 返回类型ArkWeb 还提供runJavaScriptExt()和对应的JsMessageExt。当前官方 API 文档中JsMessageExt可以区分字符串、数值、布尔值、ArrayBuffer、数组等结果类型。不过 Bridge 回调还有另一个问题数据不能直接裸拼到 JavaScript 源码中。例如不要这样写constscriptwindow.onResult(${message});如果message本身包含引号、换行甚至 JavaScript 片段最终生成的脚本可能改变原来的语义。示例采用JSON.stringify(responseJson)先把参数编码成合法的 JavaScript 字符串字面量再拼入要执行的函数调用。实际项目里这一步很容易因为“正常中文字符串都能工作”而被忽略。八、返回结果和异步调用怎么设计同步调用比较直接H5 invoke() ↓ ArkTS 执行 ↓ return JSON ↓ H5 Promise resolve异步调用不能假设 ArkTS 的业务会立即结束。所以这里增加了requestIdreq_172...流程变成H5 创建 requestId ↓ pendingCalls 保存 Promise ↓ NativeBridge.invoke() ↓ ArkTS 返回 pendingtrue ↓ ArkTS 异步任务执行 ↓ runJavaScript() ↓ HarmonyBridge.__resolve() ↓ 按 requestId 找到 Promise ↓ resolve / reject这套结构还解决了并发问题。如果同时发出三个请求req_1 req_2 req_3即使返回顺序变成req_3 req_1 req_2H5 也能根据 ID 找回对应的 Promise而不是依赖“谁先请求谁先返回”。如果业务更适合持续、高频的数据交换而不是 RPC 式的一问一答则可以评估 ArkWeb 官方提供的 WebMessagePort。官方文档明确给出了createWebMessagePorts()、postMessage()、postMessageEvent()和onMessageEvent()建立双向数据通道的方案WebMessage 支持 string 和 ArrayBuffer对象数据可以先通过 JSON 序列化为 string。所以不要把所有 Web 与 Native 通信都强行塞进一种 Bridge。九、不可信网页为什么不能随意暴露 Native 方法JS Bridge 最需要警惕的地方其实不是参数类型而是能力边界。一旦把NativeBridge注入网页这个对象就不再只是 ArkTS 内部代码。网页脚本可以调用其中被允许的方法。如果同一个 Web 组件既加载自己的本地页面又可能跳转到外部网页却给所有来源暴露诸如getToken readUserData openNativePage deleteFile pay这样的能力Bridge 就会从通信接口变成攻击面。因此至少要把三件事做好第一只暴露必要方法。本文只注册methodList:[invoke]业务能力再在invoke()内部做白名单分发而不是把整个对象的方法都开放给网页。第二限制允许调用 Bridge 的页面来源。本文通过{scheme:resource,host:rawfile}限制到应用内本地资源来源。如果以后切换到 HTTPS 在线页面应根据实际业务域名配置对应的 JavaScriptProxy URL 权限而不是为了“省事”把来源范围无限放大。第三Bridge 内部仍然要校验 method 和参数。网页传来{method:anything}不能直接通过反射或动态属性访问执行任意 Native 方法。本文采用明确的switch(request.method)未知方法直接返回错误Unknown methodBridge 应该被当作应用对 Web 开放的一组 API而不是 ArkTS 世界的一扇后门。十、几个容易理解错的地方1.runJavaScript()不等于“调用固定 H5 API”它本质上执行的是 JavaScript 脚本。所以runJavaScript(htmlTest())可以调用网页函数也可以执行其他合法 JavaScript。官方 FAQ 也采用在onPageEnd后通过runJavaScript()操作页面 DOM 的方式说明这一能力。2. Bridge 可用时机和页面加载时机不是一回事WebviewController必须和 Web 组件建立关联后实例方法才能正常工作。而网页中的函数又必须已经进入当前页面上下文。因此如果 ArkTS 一创建页面就立即runJavaScript(window.xxx())但 H5 还没有定义window.xxx自然无法得到预期结果。需要应用启动后主动向 H5 推送初始化数据时可以结合 Web 页面生命周期设计初始化时机而不是用固定延时“猜”页面什么时候准备好。3. 对象参数最好建立自己的协议不要让 Bridge 一会儿传对象、一会儿传数组、一会儿传多个位置参数。统一成 JSON 协议后id method params日志、错误处理、版本升级都会简单很多。4. Bridge 不再使用时要解除注册JavaScriptProxy 不应该只注册不释放。页面或 Bridge 生命周期结束后应结合页面结构调用deleteJavaScriptRegister()解除已经注册的对象。十一、实际项目中怎么排查如果 H5 调 ArkTS 没反应可以按下面顺序查先确认组件。当前页面到底使用的是应用 ArkWebWeb还是元服务AtomicServiceEnhancedWeb。后者目前不能直接套用本文的runJavaScript()/registerJavaScriptProxy()方案。再确认版本。HarmonyOS 7 对应 API 26.0.0项目 SDK、设备系统版本和实际调用接口要对应。检查 JavaScript 是否开启以及 Bridge 是否完成注册。H5 可以先打印window.NativeBridge确认对象是否存在。检查来源权限。如果配置了javascriptProxyPermission当前网页的 scheme、host、port、path 必须落在允许范围内。检查方法名。methodList中没有暴露的方法不能按已注册 Bridge 方法使用。检查参数协议。JSON 是否能正常解析字段类型是否符合约定。检查回调时机。ArkTS 调用 H5 函数时该函数是否已经在当前 document 中定义。最后看 requestId。异步回调到达 H5 后pendingCalls中是否仍存在对应 ID。这套顺序比一上来怀疑 ArkWeb 内核更容易缩小问题范围。十二、什么时候改用 WebMessagePortJavaScriptProxy runJavaScript()很适合“调用一个能力等待一个结果”的 RPC 风格。例如getUserInfo openNativePage chooseFile queryConfig startNativeTask如果需求变成持续交换数据Native 连续推送状态 H5 高频发送消息 双方长期保持通信通道就应该评估 WebMessagePort。官方的数据通道方案是由应用侧创建两个消息端口把其中一个通过postMessage()交给前端页面双方随后分别持有端口进行通信并在不再使用或 Webview 销毁前关闭端口。也就是说Bridge 的设计重点不是“找到唯一正确的 API”而是先判断自己的通信模型。开发经验总结这套最小实践真正值得留下来的不是sum()或delayEcho()而是五个设计点。一是把双向通信拆清楚。H5 调 ArkTS 由 JavaScriptProxy 建立入口ArkTS 主动回调 H5可以使用runJavaScript()。二是不要让业务层直接依赖 ArkWeb。用统一的HarmonyBridge.call(method,params)把平台差异收口。三是异步调用一定要有 requestId。只要存在并发请求就不能依赖调用顺序匹配返回结果。四是 Bridge 本身就是安全边界。暴露的方法越少越好来源范围越明确越好Native 侧仍然需要验证 method 和参数。五是根据通信模型选机制。一次请求一次返回适合 JS Bridge持续双向消息可以继续评估官方 WebMessagePort 数据通道。HarmonyOS 7 已正式进入 API 26 开发阶段版本升级时除了关注新增 API也应该重新检查这类跨运行环境接口的权限边界和生命周期。官方升级指南明确建议应用结合 API 变化进行适配评估。如果项目里已经有一套 Android/iOS WebView Bridge也可以进一步思考一个问题**业务层协议能不能保持不变只把 HarmonyOS ArkWeb 的 JavaScriptProxy 和runJavaScript()封装成新的平台适配层**做到这一点之后混合页面真正需要维护的就不再是三套 Bridge而是一套协议、多个平台实现。如果觉得有帮助别忘了点个赞关注支持一下~喜欢记得关注别让好内容被埋没
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。