Stage 模型到底是什么?先把 AbilityStage、UIAbility、WindowStage 的关系画清楚【鸿蒙心迹】

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀
全文目录:
前言
刚接触 HarmonyOS Stage 模型时,有几个名字很容易混在一起:HAP、AbilityStage、UIAbility、WindowStage,还有每天都在写的 ArkUI Page。
尤其是 EntryAbility.ets 里那句 windowStage.loadContent('pages/Index'),看起来只是加载首页,背后其实已经串起了 Stage 模型最核心的一条链路。
这一篇不展开页面路由、多窗口、ExtensionAbility,也不讨论复杂生命周期,只做一件事:把一个普通 Stage 工程从 HAP 到 ArkUI Page 的整体骨架画清楚。
截至 2026 年 9 月,HarmonyOS 7 已正式发布,对应 API 26。Stage 模型仍然是当前 HarmonyOS 应用开发的核心应用模型;官方文档明确说明,Stage 模型提供 AbilityStage、WindowStage 等对象,并将应用组件管理和窗口管理在架构层面解耦。
一、先别把 UIAbility 当成“一个页面”
理解 Stage 模型,第一个需要纠正的概念就是:
UIAbility 不是 ArkUI Page。
官方对 UIAbility 的定义是“包含 UI 的应用组件”,主要承担与用户交互相关的应用组件职责;而 ArkUI Page 才是真正组织和展示界面内容的页面。官方同时明确指出,每个 UIAbility 实例都会与一个 WindowStage 实例绑定,WindowStage 包含主窗口,主窗口为 ArkUI 提供绘制区域。
所以,更合适的理解不是:
UIAbility = 页面
而是:
UIAbility
│
└── WindowStage
│
└── Window
│
└── ArkUI Page
页面最终需要有一个窗口承载,而 UIAbility 与窗口之间的连接点,就是 WindowStage。
这也是为什么 DevEco Studio 创建的 Stage 工程里,首页不是在 UIAbility 的 build() 中直接写出来,而通常是在 onWindowStageCreate() 中调用:
windowStage.loadContent('pages/Index');
官方当前文档中的 UIAbility 示例仍然采用这种组织方式。
二、HAP 又处在什么位置
再往上一层看 HAP。
官方术语文档给出的定义很明确:HAP(Harmony Ability Package)是应用安装和运行的基本单元,由代码、资源、第三方库和配置文件等内容组成,分为 entry 和 feature 两种类型。
这里容易产生一个误区:
HAP、AbilityStage、UIAbility 是不是三个并列的运行对象?
不是。
HAP首先是包和模块层面的概念。例如工程中的 entry Ability 类型 Module 构建后形成 HAP。
AbilityStage 则是这个层级在运行期非常关键的对象。
官方 Stage 模型文档明确说明:
每个 Entry 类型或者 Feature 类型的 HAP,在运行期都有一个 AbilityStage 类实例;当 HAP 中的代码首次被加载到进程中时,系统会先创建 AbilityStage 实例。该 HAP 中定义的 UIAbility 实例化后,会与这个 AbilityStage 实例产生关联。
因此可以先得到第一层关系:
Entry / Feature HAP
│
└── AbilityStage
│
├── UIAbility A
└── UIAbility B
注意这里说的是一个 HAP 可以定义多个 UIAbility,并不是“一个 HAP 对应一个 UIAbility”。
AbilityStage 更接近“Module/HAP 级运行容器”。
官方 AbilityStage 开发指导同样说明:AbilityStage 是 Module 级组件容器,一个 AbilityStage 实例对应一个 Module;在 Module 的第一个 UIAbility 实例加载之前,会先创建 AbilityStage。
三、把五个核心对象放进同一张图
到这里,就可以把本文涉及的五个核心概念放到一张图里了。
应用
│
├── Entry HAP
│ │
│ ├── AbilityStage
│ │ │
│ │ ├── UIAbility 实例 A
│ │ │ │
│ │ │ └── WindowStage 实例
│ │ │ │
│ │ │ └── 主 Window
│ │ │ │
│ │ │ └── loadContent(...)
│ │ │ │
│ │ │ └── ArkUI Page
│ │ │
│ │ └── UIAbility 实例 B
│ │ │
│ │ └── WindowStage 实例
│ │ │
│ │ └── 主 Window
│ │
│ └── 代码 / 资源 / 配置等
│
└── Feature HAP(可选)
│
└── AbilityStage
│
└── ...
如果只记一张图,建议记这一张。
其中有三组关系尤其重要。
HAP → AbilityStage: AbilityStage 对应 Module/HAP 这一层的运行期组件容器。
UIAbility → WindowStage: 每个 UIAbility 类实例都会绑定一个 WindowStage 类实例。
WindowStage → ArkUI Page: WindowStage 管理窗口,通过 loadContent() 把 ArkUI 页面内容加载到关联窗口中。官方关于 UIContext 的说明进一步指出,一个 Ability 可以存在多个 Window,而每个 Window 通过 loadContent 加载页面后,会生成相应的 UIContent,也就是一个 ArkUI 实例。
所以真正完整的链路实际上比“Ability 打开 Page”多了一层窗口体系:
HAP
↓
AbilityStage
↓
UIAbility
↓
WindowStage
↓
Window
↓
UIContent / ArkUI Page
四、它们到底谁先创建
如果只讨论一个普通 Stage 单 HAP 应用的首次启动,可以把主干过程理解成:
HAP代码首次加载
↓
创建 AbilityStage
↓
AbilityStage.onCreate()
↓
创建 UIAbility 实例
↓
UIAbility.onCreate()
↓
创建主窗口对应的 WindowStage
↓
UIAbility.onWindowStageCreate(windowStage)
↓
windowStage.loadContent('pages/Index')
↓
创建并渲染 ArkUI 页面
↓
UIAbility 进入前台阶段
这里最关键的一条官方约束是:Module 第一个 UIAbility 实例加载之前,AbilityStage 已经被创建。 AbilityStage 的 onCreate() 正是用于 Module 初始化的生命周期回调。
UIAbility 与显示相关的职责又被拆给了 WindowStage。官方 Stage 模型概述特别指出,UIAbility 生命周期主要包含创建、销毁、前台、后台等状态,而显示相关状态通过 WindowStage 暴露。
这正是 Stage 模型名称里“Stage”的含义之一:应用组件和窗口各有自己的“舞台”,而不是把业务生命周期、窗口生命周期和页面生命周期全部揉进一个对象。
五、搭一个最小工程,把关系直接写进代码
下面不做业务功能,只建立一个最小观察场景:
- 给
entryHAP 显式配置 AbilityStage; - AbilityStage 创建时打印日志;
- UIAbility 创建时打印日志;
- WindowStage 创建后加载
pages/Index; - Index 页面显示一个简单文本。
这样做的价值不是实现功能,而是把上面的架构关系落实到工程文件中。
1. 创建 AbilityStage
DevEco Studio 的默认工程不一定自动生成自定义 AbilityStage 文件。官方指导要求:如果需要使用 AbilityStage,可以创建继承自 AbilityStage 的类,再通过 Module 配置中的 srcEntry 指定入口。
// entry/src/main/ets/myabilitystage/MyAbilityStage.ets
import { AbilityStage } from '@kit.AbilityKit';
export default class MyAbilityStage extends AbilityStage {
onCreate(): void {
console.info('StageDemo: AbilityStage onCreate');
}
}
这段代码解决的问题非常单纯:观察 HAP/Module 级运行对象何时进入初始化阶段。
这里使用当前 Kit 化导入方式:
import { AbilityStage } from '@kit.AbilityKit';
AbilityStage 属于 Ability Kit。
2. 在 module.json5 中关联 AbilityStage 和 UIAbility
下面只保留与本文关系直接相关的字段,不代表完整工程的全部配置:
{
"module": {
"name": "entry",
"type": "entry",
"srcEntry": "./ets/myabilitystage/MyAbilityStage.ets",
"mainElement": "EntryAbility",
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets"
}
]
}
}
这里最值得看的是两个 srcEntry。
Module 层的:
module.srcEntry
指向 AbilityStage。
而:
module.abilities[].srcEntry
指向具体 UIAbility。
两个字段长得很像,层级却完全不同。这是理解 Stage 工程结构时非常容易忽略的地方。官方 AbilityStage 指导明确使用 Module 的 srcEntry 指定 AbilityStage 源文件;UIAbility 则声明在 abilities 配置中。
六、UIAbility 真正负责的是哪一段
接下来创建最小的 EntryAbility:
// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.info('StageDemo: UIAbility onCreate');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
console.info('StageDemo: UIAbility onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
console.error(`StageDemo: loadContent failed, code=${err.code}`);
return;
}
console.info('StageDemo: pages/Index loaded');
});
}
onForeground(): void {
console.info('StageDemo: UIAbility onForeground');
}
onBackground(): void {
console.info('StageDemo: UIAbility onBackground');
}
onWindowStageDestroy(): void {
console.info('StageDemo: UIAbility onWindowStageDestroy');
}
onDestroy(): void {
console.info('StageDemo: UIAbility onDestroy');
}
}
这段代码真正需要关注的不是日志,而是:
onWindowStageCreate(windowStage: window.WindowStage)
系统把已经创建的 WindowStage 交给 UIAbility,然后我们调用:
windowStage.loadContent('pages/Index')
把页面内容加载进去。
官方当前 API 示例仍使用 UIAbility、window.WindowStage 和 loadContent() 这一组合,推荐的 Kit 导入分别来自 @kit.AbilityKit 与 @kit.ArkUI。
这个最小骨架不涉及相机、定位、文件读取等受控能力,因此本文场景不需要额外申请运行时权限。
七、ArkUI Page 是链路最末端的 UI
页面可以保持得非常简单:
// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('Stage Model')
.fontSize(28)
Text('AbilityStage → UIAbility → WindowStage → ArkUI Page')
.fontSize(16)
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
对应的页面配置可以放在:
entry/src/main/resources/base/profile/main_pages.json
例如:
{
"src": [
"pages/Index"
]
}
官方当前工程配置资料仍采用 pages: "$profile:main_pages" 与 main_pages.json 中 src 页面列表的方式组织页面。
这里还要再区分一次:
@Entry
@Component
struct Index
这里的 @Entry 表示当前 ArkUI Page 的入口组件,它不是 entry HAP,也不是 EntryAbility。
官方 ArkUI 页面生命周期文档将 Page 定义为应用的 UI 页面,并指出使用 @Entry 修饰的自定义组件作为该页面的默认入口组件。
也就是说,HarmonyOS 工程里至少存在三个容易混淆的“入口”概念:
entry HAP
EntryAbility
@Entry ArkUI Component
名字接近,但不是一个层级。
八、几个特别容易理解错的地方
1. AbilityStage 不是 Application 的另一种写法
AbilityStage 是 Module 级组件容器。
如果应用只有一个 entry HAP,看起来它似乎就是整个应用唯一的顶层对象;但到了多 HAP 工程,这种理解马上会出问题。
官方规定,每个 Entry 或 Feature HAP 在运行期都有对应的 AbilityStage。
所以应该记:
一个应用
≠ 一个 AbilityStage
一个 Entry/Feature HAP
→ 对应 AbilityStage
2. UIAbility 不是页面容器的同义词
UIAbility 是应用组件。
WindowStage 才负责连接 UIAbility 与窗口体系,而 ArkUI 页面最终被加载到窗口对应的 UI 实例中。
如果把 UIAbility 直接等价为 Page,后面遇到多窗口、UIContext、多 UI 实例时会越来越难理解。官方关于 UIContext 的说明已经明确存在:
Ability → Window → UIContent
这样的多实例关系。
3. WindowStage 也不是 ArkUI Page
WindowStage 是窗口管理层对象。
Page 是 UI 内容。
loadContent() 恰好把二者连接起来。
因此:
windowStage.loadContent('pages/Index')
不只是“跳到首页”,更准确地说,它是在为这个 WindowStage 关联的窗口加载 ArkUI 页面内容。
4. Page 生命周期不能替代 UIAbility 生命周期
ArkUI Page 有自己的生命周期,例如 onPageShow()、onPageHide();自定义组件又有 aboutToAppear()、aboutToDisappear() 等组件生命周期。
它们和:
UIAbility.onCreate
UIAbility.onForeground
UIAbility.onBackground
UIAbility.onDestroy
不是同一个层级。
页面显示,不等于 UIAbility 被重新创建。
九、HarmonyOS 7 下应该怎么看版本
HarmonyOS 7 已于 2026 年正式发布,对应 API 26。官方开发者月刊和开发者官网均已将 HarmonyOS 7(API 26)作为当前正式版本提供配套资料。
但本文讲的 Stage 骨架不是 API 26 才出现的新能力。
当前 Ability Kit 文档显示,UIAbilityContext、AbilityStageContext 等 Stage 模型基础接口从 API version 9 开始提供,并明确标注只能在 Stage 模型下使用;HarmonyOS 7 / API 26 继续支持这些基础模型。
因此本文更合适的版本表述是:
| 项目 | 本文结论 |
|---|---|
| 当前开发背景 | HarmonyOS 7 |
| 当前正式 API | API 26 |
| 应用模型 | Stage |
| UIAbility / AbilityStage | Ability Kit |
| WindowStage / Window | ArkUI 窗口能力 |
| 示例语言 | ArkTS |
| 额外权限 | 本文最小场景不需要 |
| 特定设备限制 | 本文只讨论通用应用骨架,不使用设备专属能力 |
不要因为正在使用 API 26,就把所有基础对象写成“API 26 新增”。
十、实际项目里怎么排查 Stage 层级问题
碰到“页面没有显示”“生命周期和预想不一致”一类问题时,可以按照层级从外往内检查,而不是一开始就改 ArkUI 布局。
可以按这个顺序:
- 看 Module/HAP:当前代码到底属于哪个 entry 或 feature Module。
- 看 AbilityStage 配置:如果自定义了 AbilityStage,确认
module.srcEntry是否指向正确文件。 - 看 UIAbility 配置:确认
abilities中的name、srcEntry与实际 UIAbility 对应。 - 看 WindowStage:确认页面加载逻辑是否发生在正确的
onWindowStageCreate()中。 - 看
loadContent():检查页面路径是否正确,并处理加载错误。 - 最后看 ArkUI Page:确认页面是否已经配置、是否存在
@Entry页面入口组件以及组件树本身是否正确。
这种排查方式的核心就是顺着:
HAP
→ AbilityStage
→ UIAbility
→ WindowStage
→ Page
一层一层往下找。
如果连 WindowStage 都还没有进入预期阶段,只盯着 Index.ets 改布局通常解决不了问题。
开发经验总结
Stage 模型的对象不少,但整体骨架其实可以压缩成四句话。
第一,HAP 是包和 Module 层面的基本单元,AbilityStage 是这一层对应的重要运行期组件容器。
第二,一个 HAP 可以定义 UIAbility;UIAbility 是包含 UI 的应用组件,不等于某一个 ArkUI 页面。
第三,每个 UIAbility 实例会绑定 WindowStage,WindowStage 包含主窗口,为 ArkUI 提供绘制区域。
第四,windowStage.loadContent('pages/Index') 把窗口体系和 ArkUI 页面真正连接起来。
所以,以后再看到默认工程中的:
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index');
}
}
可以在脑子里直接展开成:
Entry HAP
↓
AbilityStage
↓
EntryAbility
↓
WindowStage
↓
Main Window
↓
loadContent
↓
pages/Index
↓
ArkUI Component Tree
把这条主线弄清楚之后,再去理解 UIAbility 生命周期、多窗口、UIContext、Navigation,甚至多 HAP 工程,很多原本零散的概念都会找到自己的位置。
下一次打开一个陌生 HarmonyOS 工程,可以先不看业务代码,沿着 module.json5 → AbilityStage → UIAbility → onWindowStageCreate → loadContent → Page 走一遍。通常几分钟就能把这个工程最基本的运行骨架摸清楚。
❤️ 如果本文帮到了你…
- 请点个赞,让我知道你还在坚持阅读技术长文!
- 请收藏本文,因为你以后一定还会用上!
- 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
更多推荐



所有评论(0)