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 三个基础设施类,让窗口链路可读、可测、可恢复。

相关推荐
星栈独行17 分钟前
翻完 Pi 源码:它和 Codex、Claude Code 有何不同
开发语言·javascript·人工智能·程序人生
没有梦想的咸鱼185-1037-166327 分钟前
AI-Python机器学习与深度学习技术:CNN/Transformer/扩散模型、SHAP可解释及Hermes智能体自动化
人工智能·python·深度学习·机器学习·chatgpt·cnn·transformer
qq_4480111639 分钟前
C语言中的变量和函数的定义与声明
android·c语言·开发语言
AI探索先锋1 小时前
AMD 2nm 芯片炸裂、欧洲首家人形机器人独角兽诞生、AI Agent 互联标准打响:10 条信号看懂产业变局|今日科技 AI 机器人快讯
大数据·人工智能·深度学习·搜索引擎·机器人
落落Plus1 小时前
浏览器缓存机制详解
javascript·缓存·浏览器缓存
卡梅德生物科技小能手1 小时前
卡美德生物科普:RSPO3(R - 脊椎蛋白 3)
经验分享·深度学习·生活
孫治AllenSun2 小时前
【DataX】生产环境搭建DataX集群案例
java·开发语言·jvm
c238563 小时前
把 C++ 内存分配拆透:new 与 malloc 的三层血缘
开发语言·c++·算法
不言鹅喻3 小时前
HarmonyOS ArkTS 实战:实现一个待办清单与日程管理应用
华为·harmonyos
浅水壁虎4 小时前
vue基础(第二章 )
前端·javascript·vue.js