说明:这篇文章不是公司闭源 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 页面里有一个按钮:

function callDeviceInfo() {
  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:

var req = {
  id: 'req_001',
  action: 'getDeviceInfo',
  params: {}
}

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

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

等 ArkTS 回来时:

var p = 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') {
  return getDeviceInfo()
}

但这样越写越乱。

真实框架一般会拆成:

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐