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 管窗口和页面承载。把这两层拆清楚后,启动白屏、重复加载、销毁后回调这类问题都会更容易定位。工程上建议保留 WindowBootstrapWindowStageHolderAbilityBootRecorder 三个基础设施类,让窗口链路可读、可测、可恢复。

相关推荐
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第154题】【06_Spring篇】第14题:Spring 支持的 Bean 作用域
java·开发语言·spring·面试
这张生成的图像能检测吗2 小时前
(论文速读)CWNet:用于微光图像增强的因果小波网络
图像处理·人工智能·深度学习·机器学习·低照度图像增强
Helen_cai2 小时前
OpenHarmony Text 文本组件精细化开发与 API23 + 适配优化
华为·harmonyos
FriendshipT2 小时前
Ultralytics:解读Attention模块
人工智能·pytorch·python·深度学习·目标检测
weedsfly2 小时前
Cookie 安全三属性:HttpOnly、Secure、SameSite 分别防什么?
前端·javascript·面试
旖-旎2 小时前
QT界面优化(6)
开发语言·c++·qt
AI科技星2 小时前
基于超复数广义分形流形的电磁耦合与缪子反常磁矩几何理论
开发语言·平面·重构·概率论·量子计算·乖乖数学·全域数学
特立独行的猫A2 小时前
HarmonyOS鸿蒙三方库移植框架lycium中的HPKBUILD 与 HPKCHECK 使用指南
harmonyos