Stage 模型、UIAbility 与 ExtensionAbility 到底怎么分工?

Stage 模型、UIAbility 与 ExtensionAbility 到底怎么分工? 1. 为什么先学 Stage 模型如果说 ArkUI 决定了页面怎么写那么 Stage 模型决定了一个鸿蒙应用怎么被系统识别、调度和拉起。很多初学者把 Ability 理解成“页面”这个说法只对了一半UIAbility 确实经常承载页面入口但它更重要的身份是应用在前台与系统交互的生命周期单元。系统不是直接运行某个 ArkTS 文件而是根据应用包、模块配置、Ability 声明和 Want 信息来找到合适的入口。2. Stage 模型解决的核心问题Stage 模型相比旧模型最大的变化是把应用生命周期、窗口阶段、Ability 入口和 Extension 扩展能力拆得更清楚。开发者不再把所有事情都塞进一个前台页面里而是根据场景选择 UIAbility、ServiceExtensionAbility、FormExtensionAbility 等不同能力。这样做的目标不是增加概念而是让系统能更有序地管理进程、资源、后台行为和跨应用协作。图 1 Stage 模型中的应用、模块与能力关系3. 模块化组织entry、feature 与 shared在真实项目里Stage 模型通常会和模块化一起出现。entry 模块提供主入口和基础页面feature 模块承载可拆分业务shared 模块沉淀公共代码和资源。模块之间不是简单地按文件夹区分而是影响编译、分发、依赖和启动路径。对于一个新闻、商城或设备控制类应用来说把首页、详情、账号、后台服务、卡片能力放在同一个大入口里后期会非常难维护而 Stage 模型鼓励你从一开始就把“用户看见的入口”和“系统调度的能力”分开设计。4. UIAbility 生命周期怎么落地UIAbility 生命周期里最容易混淆的是 onCreate 和 onWindowStageCreate。onCreate 适合做轻量初始化例如读取启动参数、准备必要对象、注册少量全局监听onWindowStageCreate 才是窗口相关逻辑真正开始的地方通常在这里加载页面。把页面加载写错阶段短期可能能跑长期会导致窗口资源、页面栈和生命周期边界混乱。进入前台后应用可以恢复动画、传感器、定位或订阅退到后台时则应暂停高频刷新、保存临时状态、减少无意义计算。图 2 UIAbility 生命周期关键节点5. ExtensionAbility 的职责边界ExtensionAbility 的价值在于把特殊场景变成受控扩展。比如服务扩展适合封装后台能力和跨进程接口卡片扩展适合给桌面提供轻量信息输入法、分享、备份、无障碍等能力也都有自己的扩展入口。系统通过这些扩展类型知道你的应用想做什么再按权限、生命周期和调度规则决定能不能做、何时做、做到什么程度。这个设计能减少应用随意拉起后台进程也让不同应用之间的协作更可控。6. 页面流转Ability 与 Navigation 的分工页面跳转也要分层理解。跨业务入口、跨任务栈的跳转通常和 Ability 相关同一个 Ability 内的页面流转更适合使用 Navigation、NavDestination 等页面导航机制。把每个页面都拆成一个 UIAbility会让任务栈复杂、启动成本增加、回退行为难以预测反过来把所有入口都塞进一个页面栈也不利于系统调度和外部拉起。比较稳妥的策略是少量 UIAbility 承担入口边界大部分业务页面放在 Ability 内部导航。图 3 常见 ExtensionAbility 场景7. Want 参数与跨入口拉起Want 是系统寻找目标能力的重要载体。它可以包含 bundleName、abilityName、moduleName、parameters 等信息。对于应用内跳转Want 帮你定位目标入口对于跨应用拉起Want 还涉及隐式匹配、权限和安全边界。开发时不要把 Want 当成普通参数 Map 随便传尤其是涉及用户隐私、账号态、文件路径时要确认目标方是否可信、参数是否必要、是否存在越权访问风险。图 4 Ability 任务栈与页面内导航8. 生命周期不要堆业务从工程实践看Stage 模型最怕“生命周期里堆业务”。有些项目会在 onCreate 里做网络请求、数据库迁移、图片预加载、远端服务连接最后启动变慢、失败路径混乱。更好的方式是把生命周期回调当成调度点它只决定何时启动、暂停、释放某类任务具体业务逻辑放进独立服务类或状态管理层。这样页面、服务、卡片都可以复用同一套业务能力而不是每个入口写一份。9. 后台能力要遵守系统边界还有一个容易被忽视的点是后台能力边界。HarmonyOS 对后台行为有明确约束不是页面退到后台后仍然可以无限运行。需要持续播放、位置、下载、同步等场景时要查看对应后台任务或扩展能力要求按系统规范申请和实现。否则在测试机上偶尔可用上线后就可能遇到后台被挂起、服务被回收、通知不合规等问题。10. 团队协作的三张清单在团队协作中可以把 Stage 模型设计拆成三张清单。第一张是入口清单列出哪些能力会被桌面、通知、卡片、其他应用或系统拉起第二张是生命周期清单列出每个入口在创建、前台、后台、销毁阶段要启动和停止哪些资源第三张是依赖清单列出 entry、feature、shared 之间的依赖方向。只要这三张清单清楚后续新增功能时就不容易把代码塞错位置。11. 登录态、进程恢复与冷启动如果应用包含登录态还要特别注意 Ability 重建和进程恢复。系统可能在资源紧张时回收应用进程用户再次进入时并不一定沿着你想象中的完整启动路径恢复。重要状态不应该只放在内存里必要时要通过首选项、数据库、缓存文件或服务端重新拉取恢复。UIAbility 收到 Want 后也要能处理冷启动和热启动两种情况不能默认所有管理器都已经初始化完成。12. 测试入口不能只测桌面图标测试 Stage 模型时不要只测点击桌面图标进入。还应覆盖从通知进入、从卡片进入、从分享面板进入、从外部 Want 拉起、旋转屏幕、切到后台再返回、清理后台后重新进入等路径。很多生命周期问题只会在这些路径里暴露例如页面重复加载、订阅没有注销、后台回来后数据不刷新、重复创建窗口资源等。把这些路径列入自测用例比上线后凭日志猜问题可靠得多。13. 项目复盘中的坏味道做项目复盘时可以重点检查三个典型坏味道。第一某个 Ability 文件越来越长既管页面加载又管账号、网络、定位、数据库说明边界已经失控第二多个 Extension 重复初始化同一套业务对象说明公共能力没有下沉第三页面跳转链路里到处拼 Want 参数说明路由和参数协议没有统一封装。发现这些问题时不要急着重写全部代码先抽出启动管理、路由管理和业务服务三层逐步把职责挪回正确位置。14. 本文小结总结一下Stage 模型不是一组生命周期函数的集合而是一套应用组织方式。它回答三个问题应用有哪些入口系统如何调度这些入口不同场景的能力应放在哪种扩展里。写鸿蒙应用时先把模块、Ability、Extension、Navigation 和 Want 的边界画清楚再写页面和业务代码后期维护会轻松很多。对于新人来说先学会画入口关系图再写实现代码是理解鸿蒙工程结构最快的方法。15. Stage 模型实践对比表设计点不推荐写法推荐写法生命周期在 onCreate 堆网络、数据库和页面逻辑生命周期只做调度耗时逻辑下沉到服务类页面组织每个页面都做成 UIAbility入口用 UIAbility内部页面用 Navigation 管理后台能力页面退后台后继续随意运行按场景选择 Extension 或后台任务能力模块划分所有业务都放 entry 模块entry 承担入口feature/shared 承担业务和复用16. 案例代码轻量 UIAbility 入口export default class EntryAbility extends UIAbility {onCreate(want: Want): void {// 只做轻量初始化和启动参数解析}onWindowStageCreate(windowStage: window.WindowStage): void {windowStage.loadContent(pages/Index);}onBackground(): void {// 暂停高频刷新、保存临时状态}}这段代码体现的是“生命周期只做调度”的思路onCreate 不直接加载页面也不做耗时业务onWindowStageCreate 负责窗口内容加载onBackground 负责暂停或保存状态。真实项目里可以把账号恢复、埋点初始化、数据预热等逻辑封装到单独的 AppStartupManager 中再由生命周期回调触发。17. 案例代码使用 Want 打开指定业务入口let want: Want {bundleName: com.example.shopping,abilityName: EntryAbility,parameters: {targetPage: OrderDetail,orderId: 202606010001}};this.context.startAbility(want).then(() {console.info(start ability success);}).catch((err: BusinessError) {console.error(start ability failed: ${err.code}, ${err.message});});Want 适合描述“我要打开哪个能力并携带哪些启动参数”。如果是应用内部页面跳转可以在 EntryAbility 接收参数后交给路由层处理如果是跨应用拉起要特别关注目标 Ability 是否导出、参数是否包含敏感数据、失败时是否有兜底页面。18. 案例代码ServiceExtensionAbility 暴露后台能力export default class SyncService extends ServiceExtensionAbility {onCreate(): void {console.info(sync service created);}onConnect(want: Want): rpc.RemoteObject {// 返回远端对象供 UIAbility 通过 RPC 调用后台能力return new SyncRemoteObject(com.example.SyncService);}onDisconnect(want: Want): void {console.info(sync service disconnected);}}ServiceExtensionAbility 更适合封装“页面不应该直接做”的能力例如设备连接、后台同步、长任务调度或跨进程接口。它不是万能后台常驻方案仍然要遵守系统生命周期和权限规则。页面只负责发起连接和展示状态服务负责业务能力。19. 案例代码Ability 内部用 Navigation 管理页面流EntryComponentstruct MainPage {private pathStack: NavPathStack new NavPathStack();build() {Navigation(this.pathStack) {Column() {Button(查看订单详情).onClick(() {this.pathStack.pushPath({ name: OrderDetail, param: 202606010001 });})}}.navDestination(this.pageMap)}}如果只是从首页进入详情页、编辑页、结果页通常不需要再启动一个新的 UIAbility。Navigation 可以把页面流控制在同一个 Ability 内回退行为更清晰启动成本也更低。UIAbility 负责入口边界Navigation 负责页面层级这是更推荐的分工。