UIAbility 与 WindowStage:窗口创建、加载、销毁的完整链路
在 Stage 模型里,UIAbility 负责应用组件生命周期,WindowStage 负责承载页面窗口。很多窗口问题表面看是页面白屏、重复加载、销毁后回调还在执行,本质都是 Ability 和窗口职责没有拆开。

这篇按真实启动过程拆解:Ability 创建、窗口创建、页面加载、窗口销毁、资源释放,每一步都说明代码应该放在哪里。


1. UIAbility 不等于页面容器
UIAbility 是系统调度组件实例的入口,WindowStage 才是页面加载和窗口管理的直接对象。把页面加载写进 onCreate,短期可能还能跑,但窗口未就绪时就容易出现时序问题。
ts
export default class EntryAbility extends UIAbility {
onCreate(): void {
AbilityBootRecorder.mark('abilityCreate');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
AbilityBootRecorder.mark('windowCreate');
WindowBootstrap.loadMain(windowStage);
}
}
代码解释:
onCreate只记录 Ability 已创建,不做页面加载。- 页面加载交给
onWindowStageCreate,符合系统窗口创建顺序。 WindowBootstrap独立出来后,测试和复用会更简单。
2. 页面加载要处理失败分支
很多 Demo 只写 loadContent('pages/Index'),不处理错误。实际项目中,页面路径错误、资源异常、初始化失败都可能导致白屏。
ts
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x20260702;
export class WindowBootstrap {
static loadMain(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (error) => {
if (error.code) {
hilog.error(DOMAIN, 'WindowBootstrap', 'load Index failed=%{public}d', error.code);
WindowBootstrap.loadFallback(windowStage);
return;
}
hilog.info(DOMAIN, 'WindowBootstrap', 'Index loaded');
});
}
private static loadFallback(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/ErrorPage');
}
}
代码解释:
- 主页面加载失败时给用户一个错误页,比白屏更可控。
- 日志记录错误码,方便定位是路径问题还是资源问题。
- 兜底页要尽量轻量,不依赖复杂业务初始化。
3. 启动任务不要阻塞窗口加载
用户感知的是首屏出现速度。数据库预热、远程配置、埋点初始化不应该全部挡在 loadContent 前面。
ts
export class StartupTaskRunner {
static async runAfterFirstFrame(): Promise<void> {
await Promise.allSettled([
RemoteConfigService.load(),
AnalyticsService.prepare(),
CacheWarmService.prepareHomeData()
]);
}
}
export class WindowBootstrap {
static loadMain(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (error) => {
if (!error.code) {
StartupTaskRunner.runAfterFirstFrame();
}
});
}
}
代码解释:
- 首屏加载成功后再跑非关键初始化,减少冷启动等待。
Promise.allSettled避免一个非关键任务失败影响其他任务。- 关键初始化和非关键初始化要分组,不能全部塞进启动入口。
4. 保存 WindowStage 引用要谨慎
有些业务需要操作窗口,例如设置亮度、沉浸式、窗口属性。可以持有当前 WindowStage,但必须在销毁时清理,避免引用过期。
ts
export class WindowStageHolder {
private static current?: window.WindowStage;
static attach(stage: window.WindowStage): void {
WindowStageHolder.current = stage;
}
static detach(): void {
WindowStageHolder.current = undefined;
}
static get(): window.WindowStage | undefined {
return WindowStageHolder.current;
}
}
代码解释:
attach在窗口创建时调用,detach在窗口销毁时调用。- 调用方必须处理
undefined,不能假设窗口一直存在。 - 不建议把
WindowStage到处传递,统一 holder 更便于治理。
5. onWindowStageDestroy 是释放资源的边界
页面销毁不等于 Ability 销毁,窗口销毁也不等于进程退出。窗口相关监听、临时缓存、页面级任务应在 onWindowStageDestroy 释放。
ts
export default class EntryAbility extends UIAbility {
onWindowStageDestroy(): void {
WindowStageHolder.detach();
WindowEventBus.clear();
PageTaskRegistry.cancelAll();
hilog.info(DOMAIN, 'EntryAbility', 'window destroyed');
}
}
代码解释:
- 窗口引用、事件监听、页面任务都属于窗口生命周期资源。
- 清理动作集中在销毁回调里,比页面各自释放更可靠。
- 日志能帮助判断白屏或回调异常是否来自旧窗口。
6. 窗口事件要统一订阅和取消
窗口尺寸变化、焦点变化、前后台变化都可能影响页面布局。不要让多个页面直接订阅底层窗口事件,否则很难确保取消。
ts
export class WindowEventBus {
private static listeners: Array<() => void> = [];
static addCleaner(cleaner: () => void): void {
WindowEventBus.listeners.push(cleaner);
}
static clear(): void {
for (const cleaner of WindowEventBus.listeners) {
cleaner();
}
WindowEventBus.listeners = [];
}
}
代码解释:
- 订阅窗口事件时同步登记取消函数。
- 窗口销毁时统一执行,避免漏取消。
- 如果项目有多个窗口,应按窗口 id 分组管理。
7. 加载顺序要可观测
启动白屏最怕"感觉慢"。记录 Ability 创建、窗口创建、页面加载完成、首个业务数据返回四个时间点,能快速判断瓶颈。
ts
export class AbilityBootRecorder {
private static marks = new Map<string, number>();
static mark(name: string): void {
AbilityBootRecorder.marks.set(name, Date.now());
}
static duration(start: string, end: string): number {
const s = AbilityBootRecorder.marks.get(start) ?? 0;
const e = AbilityBootRecorder.marks.get(end) ?? 0;
return e > s ? e - s : 0;
}
}
代码解释:
mark记录关键节点时间。duration计算两个节点间耗时,用于日志和性能看板。- 建议只记录阶段耗时,不要在启动链路里做复杂计算。
8. 窗口加载失败的兜底页也要能退出
错误页不能只是提示失败,还要提供重试、退出或反馈入口。否则用户只能杀进程。
ts
export class WindowRecoverAction {
static retry(windowStage: window.WindowStage): void {
WindowBootstrap.loadMain(windowStage);
}
static report(errorCode: number): void {
FeedbackService.open({
scene: 'window_load_failed',
errorCode: String(errorCode)
});
}
}
代码解释:
- 重试逻辑复用主加载流程,不再复制一套。
- 反馈时带上错误场景和错误码,客服或日志平台才有信息可查。
- 如果兜底页本身也加载失败,应记录严重错误并退出当前 Ability。
9. 验收清单
| 检查项 | 通过标准 |
|---|---|
onCreate |
不直接加载页面 |
onWindowStageCreate |
负责主页面加载和失败处理 |
| 非关键初始化 | 不阻塞首屏 |
WindowStage 引用 |
创建时保存,销毁时释放 |
| 错误页 | 可重试、可反馈、可退出 |
10. 小结
UIAbility 管组件生命周期,WindowStage 管窗口和页面承载。把这两层拆清楚后,启动白屏、重复加载、销毁后回调这类问题都会更容易定位。工程上建议保留 WindowBootstrap、WindowStageHolder、AbilityBootRecorder 三个基础设施类,让窗口链路可读、可测、可恢复。