为什么需要 Stage 模型
早期的 FA 模型把页面、业务逻辑与能力入口揉在同一个 Ability 里,应用规模变大后,模块边界模糊、内存与进程管理能力弱的问题开始暴露。Stage 模型重新划分了职责:Ability 只负责“能力”,窗口由 WindowStage 管理,模块由 Module 组织,进程由系统按能力类型调度。
这套划分带来三个实际好处:模块可以独立编译与按需加载;同一应用的不同能力可以运行在不同进程,互不影响;应用的状态管理有明确的层级,便于系统做资源回收。
Stage 模型的核心是“把一个大对象拆成可独立管理的单元”,这与后端服务拆分是同一个思路。
理解它最好的方式是沿着一次启动流程走一遍:AbilityStage 创建 → UIAbility 创建 → 加载页面 → WindowStage 创建 → 页面 onPageShow。
UIAbility:页面能力的载体
UIAbility 是包含界面入口的应用组件,一个应用可以有多个。每个 UIAbility 实例都有一份独立的状态,系统会调用它的生命周期回调。
- onCreate实例创建时调用一次,适合做初始化(读取持久化数据、注册监听)。
- onWindowStageCreate窗口舞台创建后调用,在这里加载页面内容。
- onForeground / onBackground应用切到前台 / 退到后台,适合暂停与恢复动画、计时器。
- onWindowStageDestroy / onDestroy窗口销毁与实例销毁,用来释放资源。
一个常见误解是“所有初始化都写在 onCreate 里”。实际上与界面相关的初始化(如给页面注入依赖)放在 onWindowStageCreate 更合适,因为此时窗口已经存在。
WindowStage 与窗口生命周期
WindowStage 是窗口舞台,负责承载应用窗口。你可以把它理解为“应用与显示系统的中间层”:它管理主窗口的创建、前后台切换与销毁,页面的实际内容通过 windowStage.loadContent 加载进来。
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'EntryAbility', 'onCreate, want=%{public}s', want.abilityName);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(0x0000, 'EntryAbility', 'onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'EntryAbility', 'loadContent 失败: %{public}s', JSON.stringify(err));
return;
}
hilog.info(0x0000, 'EntryAbility', 'loadContent 成功');
});
}
onBackground(): void {
hilog.info(0x0000, 'EntryAbility', 'onBackground'); // 适合暂停计时器、释放相机
}
onWindowStageDestroy(): void {
hilog.info(0x0000, 'EntryAbility', 'onWindowStageDestroy');
}
onDestroy(): void {
hilog.info(0x0000, 'EntryAbility', 'onDestroy');
}
}
这段代码是标准的 UIAbility 骨架。注意 loadContent 的回调要处理错误:找不到页面或资源缺失时,错误会在这里返回,而不是让应用静默白屏。
应用的三个生命周期阶段
从应用整体看,生命周期分为三个阶段,理解它们才能正确管理资源。
| 阶段 | 时机 | 该做什么 |
|---|---|---|
| 前台(Foreground) | 界面可见、可交互 | 启动动画、恢复轮询、刷新数据 |
| 后台(Background) | 界面不可见但仍存活 | 暂停耗时任务、保存草稿、释放相机与定位 |
| 销毁(Destroy) | 实例被回收 | 注销监听、关闭数据库、释放一切持有资源 |
最容易出问题的是“退到后台还继续跑”:计时器不停、轮询不断、相机不释放,既耗电又可能被系统直接杀掉。正确做法是在 onBackground 中暂停,在 onForeground 中恢复,并把必要的收尾逻辑放在 onDestroy。
还要注意页面级生命周期与 Ability 级生命周期是两套东西:页面的 aboutToAppear / onPageShow / onPageHide / aboutToDisappear 属于组件层,Ability 的四个回调属于能力层。两者会被交替触发,写代码前先想清楚要挂在哪一层。
页面路由与页面栈管理
Stage 模型下,页面跳转通过路由器完成,页面栈由系统维护。每次 push 都会把新页面压栈,返回时弹栈;replace 则替换当前页面,不增加栈深度。
import { router } from '@kit.ArkUI';
// 跳转到详情页并传参
router.pushUrl({
url: 'pages/DetailPage',
params: { id: 1024, from: 'list' }
}).catch((e: Error) => {
console.error(`跳转失败: ${e.message}`);
});
// 在目标页面读取参数
const params = router.getParams() as Record<string, Object>;
const id = params?.['id'] as number;
// 返回上一页
router.back();
// 替换当前页(登录成功进入主页时常用)
router.replaceUrl({ url: 'pages/Home' });
三个实践要点:跳转要 catch 异常(页面未注册会失败);参数用对象传递,别拼在 URL 里;页面栈不要太深,超过三层体验就会变差,能用 Tab 或分段视图的就不该做成多层跳转。
AbilityStage、ExtensionAbility 与进程模型
AbilityStage 是模块级的入口:在 HAP 首次加载时创建,适合做模块级初始化,比如注册模块内的全局服务、初始化模块级的数据库连接。它不是应用级单例,多模块应用会有多个 AbilityStage。
ExtensionAbility 用于非界面能力:后台任务、数据共享、服务卡片、输入法等。它们没有界面,但同样有生命周期,且可能运行在独立进程中,因此不能直接共享内存状态,需要通过跨进程通信或数据共享机制协作。
进程方面,同一应用的不同能力可能被系统调度到不同进程。这意味着:静态变量不一定全局唯一,文件与数据库访问要考虑并发,跨能力通信必须走正式通道。把“不假设同进程”当成默认前提,能避免很多偶发的诡异问题。
用日志跟踪完整生命周期
生命周期问题最好的排查方式就是打日志,把顺序看清楚。下面这段代码在关键节点打印带标签的日志,启动一次应用就能看到完整的调用链。
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x0000;
const TAG = 'LifecycleDemo';
@Entry
@Component
struct Index {
aboutToAppear(): void {
hilog.info(DOMAIN, TAG, 'page aboutToAppear');
}
onPageShow(): void {
hilog.info(DOMAIN, TAG, 'page onPageShow'); // 每次页面可见都会触发
}
onPageHide(): void {
hilog.info(DOMAIN, TAG, 'page onPageHide');
}
aboutToDisappear(): void {
hilog.info(DOMAIN, TAG, 'page aboutToDisappear');
}
build() {
Column() { Text('生命周期演示').fontSize(20) }
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
典型输出顺序是:Ability onCreate → onWindowStageCreate → loadContent → page aboutToAppear → onPageShow。按 Home 键回到桌面会看到 onPageHide 与 Ability onBackground;再次进入则触发 onPageShow 与 onForeground。把这套顺序记熟,遇到“数据没刷新”“状态丢了”这类问题时,就知道该在哪里打断点。
常见误区与最佳实践
把几年下来踩过的坑整理成五条,能省下不少调试时间。
- 不要假设单进程:静态变量、单例在跨能力场景下并不可靠,共享数据请走正式通道。
- 不要在 onCreate 里做耗时操作:它会阻塞启动,应改为异步或延后到界面就绪后执行。
- 不要在 onBackground 里做长耗时收尾:系统可能很快回收,重要数据应实时持久化而不是离开时保存。
- onDestroy 里必须注销监听与释放资源,否则会造成内存泄漏,表现为越用越卡。
- 页面级与能力级生命周期不要混用,职责分开后代码会清晰很多。
生命周期不是需要背下来的知识点,而是资源管理与状态恢复的设计依据。想清楚“什么时候该申请、什么时候该释放”,一切就顺了。
理解架构之后,下一步建议回到 DevEco Studio 的使用技巧,把工程创建、预览调试与打包流程走一遍,形成完整的工作流。