从一个按钮开始,理解 ASCF 框架到底在做什么

📅 2026/7/1 17:06:14
从一个按钮开始,理解 ASCF 框架到底在做什么
从一个按钮开始理解 ASCF 框架到底在做什么说明本文只基于华为 ASCF 公开文档、个人学习理解和可公开的 Demo 思路进行整理不涉及任何公司内部源码、内部类名、私有接口、客户信息或实现细节。文章目标不是复刻真实框架而是帮助自己理解“小程序运行时框架”大概是如何分层、如何通信、如何排查问题的。最近在学习 ASCF也就是 Atomic Service Cross Framework。一开始看到这个框架时很容易把它理解成一个“WebView 套壳”。但是继续往下看会发现它其实不是一个简单的 WebView也不是一个普通组件库而是一套面向元服务场景的小程序运行时框架。它要解决的问题大概是已经有一套小程序生态的开发方式如何让这些页面、生命周期、组件、API 调用和系统能力在鸿蒙元服务里跑起来这篇文章不涉及任何内部源码细节只从公开资料、Demo 设计和个人理解出发尝试用一个最小模型解释 ASCF 的核心思想。1. 问题从哪里来假设我们有一个很简单的 H5 页面。页面里只有一个按钮buttononclickopenCamera()打开相机/buttonscriptfunctionopenCamera(){window.ascfBridge.send({id:Date.now(),action:camera.open,params:{mode:photo}})}/script这个按钮看起来很简单。但是问题来了H5 本身不能直接调用鸿蒙系统的相机能力。它最多只能调用浏览器或 WebView 暴露出来的能力。那么点击这个按钮之后真正发生了什么我们需要一套中间系统负责把H5 / 小程序 JS 调用转换成鸿蒙原生能力调用这就是 ASCF 这类运行时框架要解决的核心问题。2. ASCF 不是一个单独模块而是一套运行时如果只看表面ASCF 好像只是让小程序代码能在元服务中运行。但是换个角度看它其实至少要做三件事第一让页面显示出来。 第二让 JS 逻辑跑起来。 第三让 JS 能调用鸿蒙原生能力。所以可以先把它理解成三层ASCF Runtime ├── 底层核心层 ├── 逻辑层 └── 视图层这三层分别解决不同问题。3. 底层核心层接住鸿蒙能力底层核心层可以理解为 ASCF 在鸿蒙侧的运行时底座。它负责的不是具体业务而是把整个运行时撑起来。它大概会处理这些事情应用生命周期 Web 容器 页面路由 资源加载 JSBridge 通信 原生 API 调用 同层渲染组件 包管理 公共工具比如元服务启动后需要有人接住启动事件创建运行时上下文准备 Web 容器加载页面资源建立通信桥。这些事情就属于底层核心层的职责。可以这样理解鸿蒙应用启动 ↓ 底层核心层接管生命周期 ↓ 创建运行时上下文 ↓ 准备 Web 容器和路由系统 ↓ 启动逻辑层和视图层它更像是整个框架的地基。4. 逻辑层让小程序代码跑起来逻辑层可以理解为小程序运行时的大脑。它主要关心这些问题App 怎么注册 Page 怎么注册 Component 怎么创建 生命周期什么时候触发 事件怎么分发 API 怎么注册 JS 如何调用原生能力如果写过微信小程序对下面这些东西应该很熟悉App({onLaunch(){},onShow(){},onHide(){}})Page({data:{},onLoad(){},onShow(){},onReady(){},onUnload(){}})Component({properties:{},data:{},methods:{}})这些代码本身只是 JS 配置。逻辑层要做的事情就是把这些配置变成真正能运行的实例并且在正确的时机触发生命周期。比如页面第一次加载时加载页面 JS ↓ 执行 Page({...}) ↓ 创建页面实例 ↓ 触发 onLoad ↓ 通知视图层渲染 ↓ 视图层渲染完成 ↓ 触发 onReady所以逻辑层的重点不是“画页面”而是“组织页面逻辑”。它负责让小程序的 JS 代码像小程序一样运行起来。5. 视图层把页面画出来视图层可以理解为 ASCF 的渲染层。它关心的是页面结构如何渲染 组件如何注册 样式如何解析 用户事件如何监听 数据变化后如何更新 UI如果逻辑层负责“算”那么视图层负责“画”。比如逻辑层有一份页面数据{title:Hello ASCF,count:1}视图层要把它渲染成页面viewtextHello ASCF/textbutton点击次数1/button/view当用户点击按钮视图层捕获事件再把事件传给逻辑层用户点击 ↓ 视图层捕获事件 ↓ 通知逻辑层 ↓ 逻辑层执行事件处理函数 ↓ 逻辑层更新数据 ↓ 视图层重新渲染所以视图层不是普通页面代码而是一套面向小程序组件模型的渲染运行时。6. 三层合起来之后链路长什么样我们可以用一个按钮点击事件来串起来。假设页面上有一个按钮buttononclickchooseImage()选择图片/button点击之后希望调用鸿蒙系统能力选择图片。完整链路可以理解为用户点击按钮 ↓ 视图层捕获点击事件 ↓ 事件发送到逻辑层 ↓ 逻辑层执行 chooseImage ↓ 调用 wx.chooseImage / ascf.chooseImage ↓ JSBridge 封装请求 ↓ 底层核心层接收请求 ↓ API 模块分发能力 ↓ 调用鸿蒙原生能力 ↓ 拿到结果 ↓ 通过 JSBridge 回传逻辑层 ↓ 逻辑层更新页面数据 ↓ 视图层重新渲染这条链路很长。但它的本质就是一句话视图层负责收集用户行为逻辑层负责执行业务逻辑底层核心层负责调用鸿蒙能力。7. 为什么要有 JSBridgeJSBridge 是理解 ASCF 的关键。因为 JS 代码和鸿蒙原生能力不在同一个世界里。JS 世界里是这样的wx.getSystemInfo({success(res){console.log(res)}})鸿蒙原生侧可能是另一个调用方式。中间就需要一个桥JS API ↓ JSBridge ↓ ArkTS / Native ↓ HarmonyOS AbilityJSBridge 至少要解决几个问题请求怎么表示 参数怎么传 结果怎么返回 异步回调怎么匹配 错误怎么处理 事件怎么通知一个最小的桥接协议可以长这样{id:request_001,action:system.getInfo,params:{},callback:true}返回结果可以长这样{id:request_001,code:0,data:{platform:HarmonyOS},message:ok}这里的id很重要。因为 JS 调用原生能力通常是异步的必须知道某个返回结果对应哪个请求。8. 用一个 mini demo 理解 ASCF为了不陷入真实框架细节我们可以自己设计一个最小版运行时。目录可以这样拆mini-ascf-runtime-lab ├── h5-demo │ └── index.html │ ├── bridge-core │ ├── createBridge.ts │ ├── callbackManager.ts │ └── protocol.ts │ ├── runtime-core │ ├── launcher.ts │ ├── dispatcher.ts │ ├── routeManager.ts │ └── webContainer.ts │ ├── ability-plugins │ ├── toast.ts │ ├── storage.ts │ ├── network.ts │ └── system.ts │ └── debug-panel ├── logger.ts └── devtools.ts这个 Demo 不需要真的复刻 ASCF只要模拟核心链路就够了。目标是跑通H5 点击按钮 ↓ send(action, params) ↓ Bridge 封装请求 ↓ Dispatcher 查找能力 ↓ Plugin 执行 ↓ 结果返回给 H59. mini JSBridge 示例H5 侧可以这样写window.ascfBridge{callbacks:{},send(action,params,callback){constidreq_${Date.now()}_${Math.random()}this.callbacks[id]callbackconstmessage{id,action,params}window.NativeBridge.postMessage(JSON.stringify(message))},receive(response){constcallbackthis.callbacks[response.id]if(callback){callback(response)deletethis.callbacks[response.id]}}}页面调用window.ascfBridge.send(ui.showToast,{title:Hello ASCF},function(res){console.log(调用结果,res)})这段代码说明了一个核心思想JSBridge 不是魔法它本质上就是请求、分发、回调。10. mini Dispatcher 示例原生侧可以有一个分发器typeHandler(params:Recordstring,unknown)PromiseunknownclassDispatcher{privatehandlersnewMapstring,Handler()register(action:string,handler:Handler){this.handlers.set(action,handler)}asyncdispatch(message:{id:stringaction:stringparams:Recordstring,unknown}){consthandlerthis.handlers.get(message.action)if(!handler){return{id:message.id,code:404,message:unknown action:${message.action}}}try{constdataawaithandler(message.params)return{id:message.id,code:0,data,message:ok}}catch(error){return{id:message.id,code:500,message:String(error)}}}}然后注册能力constdispatchernewDispatcher()dispatcher.register(ui.showToast,async(params){return{shown:true,title:params.title}})这样一个最小的 API 调用链路就出来了。11. mini Runtime 示例运行时核心可以负责启动流程classMiniRuntime{constructor(privatedispatcher:Dispatcher,privatewebContainer:WebContainer){}asyncstart(){awaitthis.loadManifest()awaitthis.createWebContainer()awaitthis.injectBridge()awaitthis.loadHomePage()}privateasyncloadManifest(){console.log(加载配置文件)}privateasynccreateWebContainer(){console.log(创建 Web 容器)}privateasyncinjectBridge(){console.log(注入 JSBridge)}privateasyncloadHomePage(){console.log(加载首页)}}这个 Demo 虽然简单但它对应了真实运行时中的几个关键动作加载配置 创建容器 注入桥 加载页面 处理 API 调用理解了这个最小模型再去看更复杂的框架源码就不会迷路。12. ASCF 里的“同层渲染”怎么理解普通 Web 页面里很多东西都在 WebView 里渲染。但是某些组件比如video map camera canvas可能对性能、权限、层级、手势、系统能力有更高要求。这时就需要让原生组件参与渲染。可以粗略理解为普通组件 → Web 渲染 复杂组件 → 原生组件增强渲染这就是同层渲染相关能力的意义。它解决的不是“能不能显示一个标签”而是“复杂组件如何在 Web 容器里和页面一起协作”。13. 遇到问题时怎么判断该看哪一层学习框架源码最怕一上来就全文搜索。更好的方式是先按现象分层。问题现象优先怀疑方向应用启动失败底层核心层、生命周期入口页面白屏Web 容器、页面路由、视图层渲染app.js 没执行逻辑层启动器、配置加载Page 生命周期没触发逻辑层页面管理点击事件没反应视图层事件、逻辑层事件处理API 找不到API 注册、模块导出API 有调用但没返回JSBridge、回调管理、原生桥接video / map / camera 异常同层渲染组件、权限、原生能力页面数据更新但 UI 不变逻辑层数据更新、视图层响应式渲染构建或导入失败包管理、共享包配置这张表比记住某个文件名更重要。因为维护框架时第一步不是马上改代码而是判断问题属于哪条链路。14. 新增 API 时大概会经过哪些步骤假设要新增一个公开能力ascf.getBatteryInfo({success(res){console.log(res.level)}})从框架角度看大概需要做这些事情定义 JS API 名称 ↓ 实现参数校验 ↓ 注册 API ↓ 通过 JSBridge 发起请求 ↓ 底层核心层分发请求 ↓ 调用鸿蒙原生能力 ↓ 封装返回结果 ↓ 触发 success / fail / complete所以新增 API 不只是“写一个函数”。它至少涉及API 定义 API 注册 参数协议 桥接通信 原生能力 错误处理 回调机制 测试验证如果有一天需要维护或新增 API就可以按照这条链路去拆任务。15. 为什么自己写一个 mini runtime 有价值学习这种框架光看源码很容易晕。因为真实工程里会有大量边界逻辑兼容逻辑 异常处理 版本判断 性能优化 历史包袱 内部适配 工程化配置一上来就看这些很容易抓不到主线。更好的方式是先自己写一个最小模型。它不需要完整也不需要强大。只要能跑通下面这条链路就已经很有价值页面点击 ↓ JSBridge 请求 ↓ 原生分发 ↓ API 执行 ↓ 结果回调 ↓ 页面更新这个过程会让你真正理解为什么需要运行时 为什么需要桥接层 为什么需要 API 注册 为什么需要回调管理 为什么视图层和逻辑层要分开 为什么复杂组件需要原生增强16. 我的当前理解我现在对 ASCF 的理解是ASCF 是一套面向元服务的小程序运行时框架。它不是单纯的 WebView而是通过底层核心层、逻辑层和视图层的配合把小程序生态中的页面、生命周期、组件、API 和原生能力接入到鸿蒙元服务中。更简单地说底层核心层负责接住鸿蒙能力 逻辑层负责让 JS 小程序代码跑起来 视图层负责把页面渲染出来 JSBridge负责让 JS 和原生能力通信 API 系统负责把 wx.xxx / ascf.xxx 变成真实能力调用 同层渲染负责让复杂组件获得更接近原生的体验如果用一句话总结ASCF 做的事情就是在鸿蒙元服务里搭出一套“小程序可以运行的环境”。17. 下一步可以继续看什么如果继续学习源码我会按照下面顺序推进第一步启动链路 从鸿蒙生命周期入口看到 Runtime 启动。 第二步配置加载链路 看 app.json、页面配置、分包配置如何进入运行时。 第三步app.js 加载链路 看应用 JS 如何被加载、执行并触发 App 生命周期。 第四步页面创建链路 看 Page 如何注册、创建、入栈并通知视图层渲染。 第五步JSBridge 链路 看 JS API 如何发起请求、如何回调、如何处理异常。 第六步API 注册链路 看 API 如何分类、导出、注册和分发。 第七步同层渲染链路 看 video、map、camera 等复杂组件如何接入原生能力。这七步看完才算真正从“看目录”进入“看框架运行”。18. 结尾刚开始学习 ASCF不要急着问“每个文件到底做什么”。更重要的是先回答这个框架为什么存在 它解决了什么问题 它把问题拆成了哪几层 每一层负责什么 一次 API 调用会经过哪些环节 遇到问题应该从哪里排查只要这几个问题想清楚后面再看源码就不是在黑暗里摸索而是在验证自己的架构地图。这也是我接下来学习 ASCF 的方法先画地图再走链路最后看细节。参考资料华为开发者联盟ASCF / 元服务相关公开文档HarmonyOS 共享包、HAR、HSP、ohpm 相关公开文档个人公开 Demo 思路mini runtime、JSBridge、Web 容器、API Dispatcher适合继续补充的 Demo 方向后续如果继续写 Demo可以按下面几个方向逐步完善1. 先实现 H5 调 ArkTS 的最小 JSBridge 2. 再实现 API Dispatcher 和插件注册机制 3. 再加一个 Debug Panel展示每次 API 调用日志 4. 再模拟 Page 路由和生命周期 5. 最后尝试把公共能力拆成 HAR 包给其他鸿蒙项目导入使用这样就能把“看懂框架”逐步转成“自己能写一个小型框架”。