当前位置: 首页 > news >正文

为什么需要双线程通信、JavaScriptProxy 和 runJavaScript 分别干什么

说明:这篇文章不是公司闭源 ASCF 源码解析,而是基于 HarmonyOS ArkWeb 官方文档、ASCF/原子服务公开说明,以及我自己的harmony-ASCF-demo梳理出来的学习笔记。目标是把“为什么需要双线程通信、JavaScriptProxy 和 runJavaScript 分别干什么”讲清楚。

1. 我一开始的误解

刚接触 ASCF 的时候,我以为 H5 调鸿蒙能力就是一句:

window.ascfBridge.send(...)

然后 ArkTS 收到以后返回结果,页面展示。

后来写 demo 才发现,这里面其实不是一条简单的函数调用链,而是两条方向相反的通信链路:

H5 → ArkTS:javaScriptProxy ArkTS → H5:runJavaScript

这两条链路拼起来,才是一个完整的 JSBridge 闭环。

如果只看到window.ascfBridge.send(),很容易误以为“前端直接调到了鸿蒙能力”。实际上,H5 调到的只是 ArkTS 暴露出来的一个桥接方法,真正的能力调用、参数校验、权限判断、结果封装,都发生在宿主侧。


2. 为什么需要“双线程”这个概念?

可以先从小程序的运行模型理解。

普通 H5 页面里,页面渲染、DOM 操作、业务 JS、用户点击事件,很多东西都混在 WebView 里。这样做很方便,但问题也明显:

  • 业务 JS 太重,页面可能卡。
  • H5 可以直接操作页面环境,平台不容易管控。
  • Native 能力如果直接暴露给页面,安全风险会很大。
  • 页面展示和底层能力调用混在一起,后期很难维护。

所以类小程序框架通常会把运行环境拆开:

渲染层:负责页面显示、用户交互、WebView 渲染 逻辑层:负责业务逻辑、API 调用、生命周期、数据处理

放到我现在理解的 ASCF 场景里,可以先这样记:

UI / 渲染层:WebView 里的 H5 页面 逻辑 / 宿主层:ArkTS 容器、JSBridge、Dispatcher、Native 能力

这里的“双线程”不是让我们死记线程名字,而是理解一种设计思想:页面负责展示,宿主负责能力,二者通过桥接协议通信。


3. ASCF 里一次调用到底怎么走?

以我的 demo 为例,H5 页面里有一个按钮:

functioncallDeviceInfo(){run('getDeviceInfo')}

点击后会进入统一的调用方法:

ascf.call(action,params,options)

然后它会生成一个请求对象:

{version:'1.0',id:'req_001',action:'getDeviceInfo',params:{},timeout:5000}

最后发给 ArkTS:

window.ascfBridge.send(JSON.stringify(req))

这一句就是 H5 调 ArkTS 的入口。

但问题来了:window.ascfBridge是哪里来的?

它不是浏览器天然存在的对象,而是 ArkTS 侧通过 Web 组件的javaScriptProxy注入进去的。

大概像这样:

.javaScriptProxy({object:this.bridge,name:'ascfBridge',methodList:['send'],controller:this.controller})

这段配置的意思可以理解为:

把 ArkTS 里的 this.bridge.send 方法, 暴露给 H5 页面, 在 H5 里叫 window.ascfBridge.send。

注意,不是把整个WebviewController暴露给 H5,也不是把 ArkTS 所有方法都扔给页面。H5 能调什么,取决于methodList里暴露了什么。

所以如果只写了:

methodList:['send']

那么 H5 侧能调用的就是:

window.ascfBridge.send(...)

而不是:

window.ascfBridge.dispatch(...)window.ascfBridge.register(...)window.ascfBridge.runJavaScript(...)

这点很重要。桥接层暴露得越少,安全边界越清楚。


4. JavaScriptProxy 负责 H5 → ArkTS

javaScriptProxy做的事情,可以用一句话概括:

把 ArkTS 对象的方法注册到前端页面,让 H5 可以调用应用侧方法。

所以这条链路是:

H5 页面 ↓ window.ascfBridge.send(request) javaScriptProxy ↓ ArkTS bridge.send(message) ↓ BridgeController ↓ Dispatcher ↓ Biz / Imp ↓ Native 能力或模拟能力

在我的 demo 里,send收到字符串以后,不会直接执行业务,而是进入统一流程:

1. 解析 JSON 2. 校验 version / id / action / params 3. 根据 action 分发 4. 到 Registry 里找对应 handler 5. 进入 Biz 层处理业务语义 6. 进入 Imp 层执行具体能力 7. 生成统一 response

这样做的好处是,H5 只需要知道:

ascf.call('getDeviceInfo')

它不需要关心:

  • 鸿蒙设备信息 API 怎么调用
  • 是否需要权限
  • 返回格式怎么封装
  • 出错时错误码怎么定义
  • 异步回调怎么对应到原来的请求

这些复杂度都应该由 ASCF 框架层处理。


5. runJavaScript 负责 ArkTS → H5

H5 发出去之后,ArkTS 处理完能力,还要把结果还给 H5。

这时候就轮到runJavaScript了。

H5 里会提前挂一个全局回调函数:

window.__ascfOnResponse=function(jsonStr){ascf._onResponse(jsonStr)}

这行代码不是 H5 自己主动调用的,而是给 ArkTS 留的“回调入口”。

ArkTS 侧处理完之后,会类似这样调用:

this.controller.runJavaScript(`window.__ascfOnResponse(${JSON.stringify(responseJson)})`)

于是 H5 侧的window.__ascfOnResponse被执行,拿到 ArkTS 回来的响应。

所以第二条链路是:

ArkTS response ↓ WebviewController.runJavaScript(...) ↓ window.__ascfOnResponse(jsonStr) ↓ ascf._onResponse(jsonStr) ↓ pending[id] ↓ resolve / reject ↓ then / catch ↓ showResult(resp) ↓ 页面展示

这就是为什么我说 JSBridge 不是一条链路,而是两条链路拼起来:

H5 调 ArkTS:javaScriptProxy ArkTS 回 H5:runJavaScript

6. requestId 为什么重要?

刚开始写 demo 的时候,我容易忽略requestId

后来发现,没有它就没法处理异步。

比如 H5 连续点了三个按钮:

getDeviceInfo getLocation getClipboardData

这三个请求可能不是按发送顺序返回的。如果没有 id,H5 就不知道哪个 response 对应哪个按钮。

所以 H5 发请求时要生成 id:

varreq={id:'req_001',action:'getDeviceInfo',params:{}}

同时在本地保存一个 pending 表:

pending[id]={resolve,reject,timer,action}

等 ArkTS 回来时:

varp=pending[resp.id]

如果找到了,就说明这次响应能对应到之前的请求,然后再执行:

resp.code===0?p.resolve(resp):p.reject(resp)

最后页面里的.then().catch()才会继续执行。

所以页面展示不是__ascfOnResponse直接改 DOM,而是:

__ascfOnResponse ↓ 找到 pending ↓ resolve / reject ↓ then / catch ↓ showResult

这也是我之前看代码时卡住的地方:我看到了window.__ascfOnResponse,但没看到它在哪里展示数据。真正展示数据的是后面的showResult(resp)


7. Dispatcher / Register / Biz / Imp 是干什么的?

如果只是 demo,其实可以在send里直接写:

if(action==='getDeviceInfo'){returngetDeviceInfo()}

但这样越写越乱。

真实框架一般会拆成:

Register:启动时注册能力 Dispatcher:运行时根据 action 找能力 Biz:处理业务语义 Imp:执行具体实现

例如:

getDeviceInfo → DeviceHandler getCurrentTime → TimeHandler getClipboardData → ClipboardHandler setClipboardData → ClipboardHandler openToast → ToastHandler

这样 H5 发来:

{"id":"req_001","action":"getDeviceInfo","params":{}}

Dispatcher 就去 Map 里找:

Map.get('getDeviceInfo')

找到了就执行,找不到就返回:

UNKNOWN_ACTION

这也是维护 ASCF 框架时非常常见的问题:H5 说“我调了,但是没反应”,你第一步就可以查 action 有没有注册、拼写是否一致、参数是否符合协议。


8. 我现在怎么理解 ASCF 双线程?

现在我会这样理解:

ASCF 不是简单地把 H5 放进 WebView。 它更像是在 WebView 和 HarmonyOS 能力之间,加了一层受控的运行时。

H5 不直接碰 Native 能力,而是:

H5 → JSBridge → ArkTS 宿主 → Dispatcher → Biz/Imp → Native 能力

Native 也不是随便把结果塞回页面,而是:

Native 结果 → 统一 response → runJavaScript → H5 callback → Promise → 页面更新

所以 ASCF 双线程通信的核心,不是“线程”这两个字,而是这三个点:

1. 渲染和逻辑分离 2. 能力调用走协议 3. 双向通信有边界

9. 这套模型对排查问题有什么帮助?

理解这条链路以后,排查问题就不会乱猜。

如果 H5 调不到 ArkTS,先查:

javaScriptProxy 是否注册成功? methodList 是否包含 send? H5 是否在 Web 容器里打开? window.ascfBridge 是否存在?

如果 ArkTS 收到了但没有结果,查:

action 是否正确? Registry 里是否注册? Dispatcher 是否找到 handler? Biz / Imp 有没有抛错?

如果 ArkTS 执行成功但 H5 没显示,查:

runJavaScript 是否执行? window.__ascfOnResponse 是否存在? response.id 是否和 pending 里的 id 一致? 是否已经超时删除 pending? showResult 是否执行?

这比单纯看日志有效很多,因为你知道每一段链路的职责。


10. 总结

这篇文章可以用一句话收尾:

JavaScriptProxy 解决 H5 如何调用 ArkTS; runJavaScript 解决 ArkTS 如何回调 H5; Dispatcher / Register / Biz / Imp 解决 ArkTS 内部如何把 action 分发到具体能力。

所以完整闭环是:

H5 按钮点击 ↓ window.ascfBridge.send ↓ javaScriptProxy ↓ ArkTS bridge.send ↓ Controller / Protocol ↓ Dispatcher / Register ↓ Biz / Imp ↓ response ↓ runJavaScript ↓ window.__ascfOnResponse ↓ Promise resolve / reject ↓ 页面展示

如果后面继续维护 ASCF 框架,我觉得重点不是背 API,而是把这条链路跑熟。

因为真实项目里的问题,大概率就出在这几类地方:

桥没有注入 action 没注册 协议不一致 权限没过 异步回调丢失 response id 对不上 页面销毁后还在回调

把这些问题串起来,ASCF 就不再是一堆陌生名词,而是一条可以一步步排查的通信链路。


官方参考

  • HarmonyOS ArkWeb:前端页面调用应用侧函数
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/web-in-page-app-function-invoking

  • HarmonyOS ArkWeb:应用侧调用前端页面函数
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/web-in-app-frontend-page-function-invoking

  • HarmonyOS ArkWeb:WebviewController API 参考
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-webview-webviewcontroller

  • HarmonyOS ArkWeb:组件安全开发建议
    https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-arkweb-component-security

  • ASCF Development Guide
    https://developer.huawei.com/consumer/en/doc/atomic-ascf/ascf-development-guide

http://www.jsqmd.com/news/1077233/

相关文章:

  • Gamma函数与正弦函数加权乘积不等式:原理、推导与应用
  • 深度技术解析:猫抓浏览器扩展如何实现高效资源嗅探的5大关键技术
  • 餐饮外卖点餐小程序源码性能优化实录(附代码)——Redis 热点缓存、接口限流与数据库索引设计
  • 低功耗IoT设备电源管理:PMIC选型与i.MX RT600系统设计实践
  • 线性回归实战指南:从面试陷阱到工业级诊断与部署
  • 7 大 AI Agent 平台深度技术横评:Coze、Dify、百炼、360智语、千帆、Copilot Studio、LangGraph 政企选型全拆解
  • 【撕开黑盒学大模型】划清玩具与生产级系统的边界:LLM Agent 的稳定性、可观测性与生态解耦思辨
  • 3步实现输入法词库无缝迁移:告别平台切换的困扰
  • Diffusers实战指南:Stable Diffusion生产级部署与调优
  • BilldDesk:免费开源的跨平台远程桌面解决方案完全指南
  • Sqribble深度解析:模板驱动的文档操作系统架构
  • 计算机毕业设计之“速餐”校园订餐系统的设计与实现
  • 全网资源下载神器res-downloader:5分钟学会智能抓取视频音频
  • 加权AM-GM不等式:从乘积极值到线性优化的降维策略
  • 如何将 iPad 同步至新电脑,且不丢失原有数据?
  • 3步掌握Flowframes:让你的视频帧率翻倍的终极AI工具
  • 2026甘肃考公机构梯队排名:从第一梯队到潜力机构,哪家更值得选?
  • 顶刊聚焦|肿瘤相关巨噬细胞(TAM)新的功能亚群 —— 机制已解构,空间待解析
  • 工业级遗传算法实战:问题驱动的GA工程化落地指南
  • 2026免费一键去图片水印的app有哪些:无广告手机软件与跨平台选择指南
  • 大型洗涤厂必看!一套好用的布草管理系统应具备哪些功能?
  • vscode到底有什么用
  • 生产级ML模型部署:从Notebook到稳定推理服务
  • VMware虚拟机Java开发环境配置失效?——20年经验总结的6类隐蔽性Host-Only网络陷阱及修复时间表
  • Winlator终极指南:3步搞定Android上的Windows应用输入控制
  • Cesium 渐变色墙体教程
  • 微创介入是“矛“,中医扶正是“盾“——杭州这家医院把两者融成了一体
  • 号码认证哪家好?关键指标与权威平台推荐
  • INT8量化实战:从FP32模型到边缘端高效推理的完整工程链
  • iOS自动化测试核心:WebDriverAgent原理、配置与Appium集成实战