UIAbility 与 WindowStage:窗口创建、加载、销毁的完整链路

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);
  }
}

代码解释:

  1. onCreate 只记录 Ability 已创建,不做页面加载。
  2. 页面加载交给 onWindowStageCreate,符合系统窗口创建顺序。
  3. 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');
  }
}

代码解释:

  1. 主页面加载失败时给用户一个错误页,比白屏更可控。
  2. 日志记录错误码,方便定位是路径问题还是资源问题。
  3. 兜底页要尽量轻量,不依赖复杂业务初始化。

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();
      }
    });
  }
}

代码解释:

  1. 首屏加载成功后再跑非关键初始化,减少冷启动等待。
  2. Promise.allSettled 避免一个非关键任务失败影响其他任务。
  3. 关键初始化和非关键初始化要分组,不能全部塞进启动入口。

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;
  }
}

代码解释:

  1. attach 在窗口创建时调用,detach 在窗口销毁时调用。
  2. 调用方必须处理 undefined,不能假设窗口一直存在。
  3. 不建议把 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');
  }
}

代码解释:

  1. 窗口引用、事件监听、页面任务都属于窗口生命周期资源。
  2. 清理动作集中在销毁回调里,比页面各自释放更可靠。
  3. 日志能帮助判断白屏或回调异常是否来自旧窗口。

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 = [];
  }
}

代码解释:

  1. 订阅窗口事件时同步登记取消函数。
  2. 窗口销毁时统一执行,避免漏取消。
  3. 如果项目有多个窗口,应按窗口 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;
  }
}

代码解释:

  1. mark 记录关键节点时间。
  2. duration 计算两个节点间耗时,用于日志和性能看板。
  3. 建议只记录阶段耗时,不要在启动链路里做复杂计算。

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)
    });
  }
}

代码解释:

  1. 重试逻辑复用主加载流程,不再复制一套。
  2. 反馈时带上错误场景和错误码,客服或日志平台才有信息可查。
  3. 如果兜底页本身也加载失败,应记录严重错误并退出当前 Ability。

9. 验收清单

检查项 通过标准
onCreate 不直接加载页面
onWindowStageCreate 负责主页面加载和失败处理
非关键初始化 不阻塞首屏
WindowStage 引用 创建时保存,销毁时释放
错误页 可重试、可反馈、可退出

10. 小结

UIAbility 管组件生命周期,WindowStage 管窗口和页面承载。把这两层拆清楚后,启动白屏、重复加载、销毁后回调这类问题都会更容易定位。工程上建议保留 WindowBootstrap、WindowStageHolder、AbilityBootRecorder 三个基础设施类,让窗口链路可读、可测、可恢复。

相关推荐
keyipatience16 分钟前
TypeScript学习(1)
前端·javascript·学习·typescript·html·css3
爱喝热水的呀哈喽29 分钟前
HEJIAN RESEARCH
开发语言·matlab
白露与泡影36 分钟前
后端 Java 面试核心八股汇总|高频面试题 + 解析,覆盖基础到进阶
java·开发语言·面试
xifangge20251 小时前
基于 JavaScript 实现 CNC 数控切削参数与螺纹公差纯前端核算引擎
开发语言·前端·javascript
“AI国潮设计-小江”1 小时前
【AIGC实战】Python+SDXL批量生成「潮汕英歌舞冰淇淋蛋糕&红桃粿咖啡」IP,固定角色形象批量出甜品商业效果图
开发语言·人工智能·python·prompt·aigc
\光辉岁月/1 小时前
3.包装类
java·开发语言·包装类
honkun61 小时前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
FRing032 小时前
JavaScript入门到精通二十四(正则表达式、元字符、修饰符)
开发语言·前端·javascript·正则表达式
老鱼说AI2 小时前
脉冲神经网络:神经网络发展历史背景
人工智能·深度学习·神经网络·机器学习·自然语言处理·学习方法
西索斯coding2 小时前
Grok 4.7 调用一直 401 怎么办?不是 Key 过期,是 Authorization 头格式问题——排查全流程 + 修复示例
服务器·开发语言·ai·php