从 Web 容器开始,理解 ASCF 元服务开发
从 Web 容器开始理解 ASCF 元服务开发为什么要写这个刚开始看 ASCF 的时候我其实不是先被代码难住而是先被概念绕住了。什么是 ASCF什么是元服务为什么又和小程序生态有关Web 容器、JSBridge、ArkTS、Native 能力这些词放在一起到底在解决什么问题如果一开始就从定义出发很容易变成背概念。所以我更愿意从一个具体问题切进去如果一个元服务里需要承载一段 H5 页面这个 H5 怎么进入 HarmonyOS 页面它又怎么调用 ArkTS 侧的原生能力这个问题一旦想清楚Web 容器、JSBridge、Native 能力分发、容器治理这些点就能串起来。我在harmony-ASCF-demo里做的练习本质上就是围绕这条链路H5 → ArkTS → 模拟 Native 能力 → ArkTS → H5这篇文章就从这条链路开始理解 ASCF 元服务开发里 Web 容器这一块到底在做什么。问题是什么在普通 ArkUI 页面里我们写的是 ArkTS 组件Text() Button() Column() List() Web()这些组件由 HarmonyOS 原生渲染。但很多业务并不是完全从零用 ArkUI 写出来的。现实里经常会有这些情况之前已经有一套 H5 页面某些活动页、营销页更适合用 Web 技术快速迭代小程序生态里已有页面或业务能力希望迁移到元服务页面展示层想用 H5但设备能力、Toast、定位、扫码等能力还得走原生这时候就会出现一个核心问题H5 可以被嵌进来但 H5 不能直接调用 HarmonyOS 原生能力。于是需要两层东西Web 容器负责把 H5 放进 ArkUI 页面里。JSBridge负责让 H5 和 ArkTS 通信。如果只加载 H5没有 JSBridge那只是一个网页。如果有 JSBridge但没有协议、分发、日志、错误处理就很容易变成一堆临时字符串调用。所以真正有价值的不是“能不能调通”而是消息格式怎么设计action 怎么分发返回值怎么回传出错了怎么提示调试时怎么看请求和响应哪些链接允许打开哪些要拦截这些问题合起来才是一个可维护的 Web 容器能力。我的理解我现在对 ASCF 元服务开发的理解可以先不从大而全的定义讲起而是从“页面承载方式”理解。ASCF 面向的是元服务和小程序生态的开发场景。它希望让开发者能用接近小程序的开发体验更高效地开发元服务。而 Web 容器这一层解决的是怎么把 Web 技术栈的页面放进 HarmonyOS 的元服务页面里并让它具备调用原生能力的能力。这有点像小程序里的页面和宿主环境H5 负责页面展示和交互ArkTS 负责宿主能力和系统能力JSBridge 负责两边通信Bridge Dispatcher 负责按 action 分发Native Ability 层负责具体能力实现Log / Monitor 负责调试和排查问题也就是说不要把 Web 容器理解成单独的Web()组件。更完整一点它应该是一套链路ArkUI 页面 ↓ Web 容器 ↓ 本地 H5 / 远程 H5 ↓ JSBridge 协议 ↓ ArkTS 分发层 ↓ Native 能力 ↓ 结果回传给 H5在我的 demo 里WebRuntimePage就是这个链路的入口。关键代码或关键链路1. 用 Web 组件加载本地 H5在 ArkUI 页面里Web 容器的入口是Web组件。我的 demo 里加载的是resources/rawfile下的本地 HTMLWeb({src:$rawfile(ascf_bridge_demo.html),controller:this.controller}).javaScriptAccess(true).domStorageAccess(true)这里有几个点src指向本地 H5 文件。controller用来控制 WebView比如后面调用runJavaScript。javaScriptAccess(true)开启 JavaScript。domStorageAccess(true)允许 Web 页面使用本地存储能力。这一步只是把 H5 放进来还没有做到 H5 和 ArkTS 互通。2. H5 调 ArkTSjavaScriptProxyH5 想调用 ArkTS需要 ArkTS 往 Web 页面里注入一个对象。在 demo 里这个对象叫window.ascfBridgeArkTS 侧通过javaScriptProxy注册.javaScriptProxy({object:this.bridge,name:ascfBridge,methodList:[send],controller:this.controller})这样 H5 就可以调用window.ascfBridge.send(JSON.stringify(request))这里我没有让 H5 直接调很多方法比如window.native.getDeviceInfo()window.native.openToast()window.native.getLocation()而是统一收口成一个send方法。原因是Bridge 方法一多管理会变复杂。统一成send(json)之后所有调用都走同一套协议。3. 先定义 Bridge 协议Bridge 最重要的不是“能调”而是“怎么描述一次调用”。一次比较完整的请求可以长这样{id:req_001,action:openToast,params:{message:Hello ASCF}}关键字段id标识一次请求方便回调和日志追踪。action告诉 ArkTS 要调用什么能力。params调用参数。返回值可以长这样{id:req_001,code:0,message:ok,data:{result:true}}关键字段id和请求对应。code错误码。message提示信息。data真正的数据。这一步很重要因为它让 Bridge 从“临时调用”变成了“协议通信”。4. ArkTS 收到消息后分发 actionH5 发来的 JSON 字符串会进入 ArkTS 的WebBridgeChannel。大概流程是H5 调 window.ascfBridge.send(json) ↓ WebBridgeChannel.send(jsonStr) ↓ JSON.parse ↓ BridgeLog 记录请求 ↓ BridgeDispatcher.dispatch(req) ↓ NativeAbilityBiz / NativeAbilityImp ↓ 生成响应 ↓ BridgeLog 记录响应我比较喜欢把它拆成几层WebBridgeChannel只负责收发消息。BridgeProtocol定义请求和响应格式。BridgeDispatcher按 action 分发。NativeAbilityBiz业务能力入口。NativeAbilityImp模拟具体原生能力。BridgeLog记录完整链路。这样以后加能力时不需要把所有逻辑都堆在 Web 页面里。比如新增一个getDeviceInfo只需要在协议和分发层补对应 action再到 Native 能力层实现。5. ArkTS 回传给 H5runJavaScriptH5 调 ArkTS 是一半ArkTS 把结果回给 H5 才是闭环。在 demo 里我约定 H5 暴露一个全局函数window.__ascfOnResponsefunction(respJson){// 处理 ArkTS 回传结果}ArkTS 侧通过runJavaScript调回去this.controller.runJavaScript(if(window.__ascfOnResponse){window.__ascfOnResponse(arg);})这样完整链路就跑通了H5 发请求 ↓ ArkTS 收请求 ↓ ArkTS 调模拟 Native 能力 ↓ ArkTS 生成响应 ↓ H5 收到响应并更新页面6. Web 容器治理不是只加载页面一个真正能用的 Web 容器不能只写Web()。还要处理页面加载开始页面加载进度页面标题页面加载完成主页面错误HTTP 错误外部链接拦截在 demo 里我做了这些状态Localprogress:number0Localtitle:stringWeb 容器LocalloadState:string未开始LocalerrorMsg:stringLocalguardMsg:string对应 Web 组件事件.onPageBegin((){this.loadState加载中}).onProgressChange((event){this.progressevent.newProgress}).onTitleReceive((event){this.titleevent.title}).onPageEnd((){this.loadState已完成}).onErrorReceive((event){this.loadState出错}).onHttpErrorReceive((event){this.guardMsgHTTP 错误 event.response.getResponseCode().toString()})还有白名单拦截privateshouldIntercept(url:string):boolean{constisHttpurl.startsWith(http://)||url.startsWith(https://)if(!isHttp){returnfalse}for(constitemofthis.allowList){if(url.startsWith(item)){returnfalse}}this.guardMsg已拦截非白名单地址urlreturntrue}这一步是我觉得最容易被忽略的。很多 demo 只做到“页面能打开”但真正开发时更常见的问题是页面打不开原因是什么加载到多少了标题有没有同步外链能不能随便跳HTTP 资源报错能不能看见H5 调 Native 的请求有没有日志这些都属于容器治理。踩坑记录1. 不要一开始就堆概念ASCF、元服务、小程序生态、Web 容器、JSBridge 这些词一起出现时新人很容易懵。我的经验是先抓住一个问题H5 怎么进入 ArkUIH5 怎么调用 ArkTS把这条链路跑通后再回头理解 ASCF 会轻松很多。2. Bridge 不要写成一堆零散方法一开始可能会想这样写window.native.openToast()window.native.getDeviceInfo()window.native.openPage()短期看很直观但后面方法越来越多就不好维护。更好的方式是统一成window.ascfBridge.send(JSON.stringify(request))所有能力都通过action分发。3. runJavaScript 回传要注意字符串安全ArkTS 拼 JS 字符串时不能直接把 JSON 拼进去。比较稳的做法是先对响应 JSON 再做一次JSON.stringify把它变成合法的 JS 字符串字面量。这样可以避免引号、换行、特殊字符导致 JS 执行失败。4. 日志面板比想象中重要真机上调 Web 容器和 JSBridge 时如果没有日志面板很难判断问题出在哪。所以我给 demo 加了两个日志BridgeLog看 H5 和 ArkTS 的桥接请求 / 响应。NetMonitor看 REST 请求和 WebSocket 帧。这两个调试能力会让 demo 从“能跑”变成“能排查问题”。5. Web 容器不是只负责显示页面Web 容器还要管加载状态错误状态页面标题资源错误HTTP 错误外链拦截JS 通信日志追踪这些才是工程里真正会遇到的问题。总结如果只从概念看ASCF 元服务开发容易显得比较抽象。但如果从 Web 容器切入就能看到一条很具体的开发链路ArkUI 页面承载 Web ↓ Web 加载 H5 ↓ H5 通过 JSBridge 调 ArkTS ↓ ArkTS 按 action 分发 Native 能力 ↓ ArkTS 用 runJavaScript 把结果回传 H5 ↓ 日志面板记录完整链路我现在对这个 demo 的定位是它不是一个业务产品而是一个 ASCF 元服务开发能力的练习场。通过它可以练到ArkUI 页面组织ArkWeb / Web 容器JSBridge 协议设计ArkTS 和 H5 双向通信Native 能力分发Web 容器治理网络请求与 WebSocket 调试多模块结构拆分对新人来说比起一开始背概念我更推荐先把这条链路跑通。只要能清楚讲出为什么需要 Web 容器 为什么需要 JSBridge Bridge 协议怎么设计 ArkTS 怎么分发 action 结果怎么回到 H5 出问题时怎么调试ASCF 元服务开发就不再只是一个名词而是一条能落到代码里的工程链路。参考资料华为开发者联盟元服务开发入门https://developer.huawei.com/consumer/cn/fa/get-started/HarmonyOS WebView CodelabUsing WebViewhttps://developer.huawei.com/consumer/en/codelab/HarmonyOS-WebView/HarmonyOS FAQjavaScriptProxy 与 registerJavaScriptProxy 区别https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkweb-20demo 仓库harmony-ASCF-demohttps://github.com/lichenyang5/harmony-ASCF-demo