在这里插入图片描述

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀

前言

新建一个 Stage 模型的 HarmonyOS 应用,打开 EntryAbility.ets,通常都会看到一段非常熟悉的代码:

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    // ...
  });
}

代码只有几行,却很容易产生一个理解偏差:onWindowStageCreate() 是不是“创建首页”的生命周期?windowStage 是不是页面?如果把 loadContent() 删除,窗口还在不在?首页究竟是在什么时候出现的?

这一篇只拆这一件事。

我们用一个最小实验,把 UIAbility、WindowStage、主窗口和 pages/Index 之间的关系理清楚。

一、先把结论说清楚:onWindowStageCreate 不是“首页创建回调”

在 Stage 模型中,每个 UIAbility 实例都会与一个 WindowStage 实例绑定。官方对 WindowStage 的定义很关键:它承担应用进程内窗口管理器的角色,并包含一个主窗口;这个主窗口为 ArkUI 提供绘制区域。换句话说,UIAbility、窗口和 ArkUI 页面不是同一个东西。

可以先把启动过程简化成下面这条链路:

UIAbility
    ↓
WindowStage 创建完成
    ↓
onWindowStageCreate(windowStage)
    ↓
得到 WindowStage
    ↓
windowStage.loadContent('pages/Index')
    ↓
将 Index 页面内容加载到关联窗口
    ↓
页面进入可显示的 UI

所以,onWindowStageCreate() 更准确的理解是:

UIAbility 对应的 WindowStage 已经创建,此时应用拿到了管理窗口和装载 UI 内容的入口。

真正把 pages/Index 接到窗口上的关键操作,是后面的 loadContent()。

华为官方 FAQ 对这层关系描述得更加直接:在 Stage 模型中,WindowStage/Window 可以通过 loadContent 加载页面、创建 UI 实例,并把页面内容渲染到关联窗口中。

这也是这一篇最需要记住的关系:

有 WindowStage,不等于已经有 Index 页面;调用 loadContent(),才把指定页面内容加载到窗口。

二、版本、Kit 和使用条件先核对

本文以 HarmonyOS 7.0 为背景。华为当前升级适配文档明确说明,HarmonyOS 7.0 对应 API version 26.0.0,并建议使用与 26.0.0 配套的开发套件进行适配。

不过,UIAbility + WindowStage 并不是 HarmonyOS 7 才新增的机制。Stage 模型相关基础能力已经存在多个 API 版本。官方当前 UIAbilityContext 文档也明确说明,该模块首批接口从 API version 9 开始支持,并且仅用于 Stage 模型。

本文实际涉及的内容很少:

项目本文使用内容
开发模型Stage 模型
HarmonyOS 7 对应版本API 26.0.0
Ability 类型UIAbility
Ability KitUIAbility
ArkUIwindow.WindowStage
核心生命周期onWindowStageCreate()
核心操作WindowStage.loadContent()
页面pages/Index
额外权限本示例不增加 requestPermissions
Native/C++不涉及
特定设备能力不涉及

代码中的导入关系保持最小即可:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

官方现有窗口开发示例同样采用 UIAbility 配合 window.WindowStage,并在 onWindowStageCreate() 中调用 loadContent()。

三、实验一:先故意不调用 loadContent

先把页面加载去掉,只保留 WindowStage 创建回调:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    console.info('[WindowStageDemo] onWindowStageCreate');
  }
}

这里最值得观察的不是某一台设备最终呈现为哪种背景,而是程序状态本身:

onWindowStageCreate() 已经被执行,但这段代码没有要求 WindowStage 加载任何 ArkUI 页面。

因此不能把“进入 onWindowStageCreate()”理解成“Index 页面已经创建”。

应用的启动界面还可能涉及 module.json5 中的 startWindowIcon、startWindowBackground 等启动界面配置。官方构建 FAQ 也说明,这些字段用于应用/元服务的启动界面图标和背景颜色。

所以实验时如果看到启动背景,也不要立即得出“Index 已经加载”的结论。

判断页面有没有真正进入这条加载链路,应该回到 loadContent()。

四、实验二:WindowStage 到底是什么

把参数打印出来并没有太大意义,更重要的是理解它在架构里的位置。

官方 Stage 模型说明中提到,每个 UIAbility 类实例都会绑定一个 WindowStage 实例,WindowStage 包含主窗口,而主窗口为 ArkUI 提供绘制区域。

也就是说:

onWindowStageCreate(windowStage: window.WindowStage): void {
  // 此时系统已经把当前 UIAbility 对应的 WindowStage 交给应用
}

这里的 windowStage 不是 Index.ets,也不是一个 ArkUI Component。

它更接近“这个 UIAbility 所对应窗口舞台的管理入口”。

这也解释了为什么很多窗口相关初始化代码会出现在这里。比如华为官方沉浸式窗口示例,就是在 loadContent() 成功后,通过 windowStage.getMainWindowSync() 获取应用主窗口,再进行窗口布局设置。

这条关系可以进一步写成:

EntryAbility
   │
   └── WindowStage
          │
          └── Main Window
                 │
                 └── ArkUI 页面内容

如果把 WindowStage 直接理解成“页面”,后面学习主窗口、子窗口、UIContext 时很容易全部混在一起。

五、实验三:真正加载 pages/Index

现在恢复最核心的一行:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    console.info('[WindowStageDemo] onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(
          `[WindowStageDemo] loadContent failed, code=${err.code}, message=${err.message}`
        );
        return;
      }

      console.info('[WindowStageDemo] loadContent success');
    });
  }
}

这段代码解决的事情非常单纯:

在 WindowStage 已经创建之后,把 pages/Index 指定为要加载到窗口中的页面内容。

华为官方多个当前示例都采用同样的调用方式:

windowStage.loadContent('pages/Index', (err) => {
  if (err.code) {
    return;
  }

  // 页面加载成功后的逻辑
});

例如窗口沉浸式处理、主窗口 UIContext 获取等代码,都放在 loadContent() 成功之后继续执行。

这里真正值得关注的不是代码长度,而是时序边界。

onWindowStageCreate() 表示 WindowStage 创建阶段已经到达;loadContent() 才负责把指定页面装载到窗口;成功回调则告诉业务代码,这次页面内容加载操作已经成功。

六、成功回调和失败回调分别意味着什么

很多默认模板里的代码只有:

if (err.code) {
  return;
}

学习阶段建议不要这么快把错误吞掉。

可以至少留下错误码和错误信息:

windowStage.loadContent('pages/Index', (err) => {
  if (err.code) {
    console.error(
      `loadContent failed, code=${err.code}, message=${err.message}`
    );
    return;
  }

  console.info('loadContent success');
});

这样做的价值不是“多打一条日志”,而是明确建立两个状态:

WindowStage 已创建
        │
        ├── loadContent 失败 → 页面没有按预期完成加载
        │
        └── loadContent 成功 → 可以继续执行依赖页面/窗口内容的后续逻辑

华为官方示例也普遍在回调中检查 err.code,失败后立即返回,成功后再获取主窗口、UIContext 或执行其他初始化。

这意味着一个很实用的编码习惯:

依赖页面已经加载完成的逻辑,不要想当然地放在 loadContent() 调用之后,而应根据具体 API 的使用条件放到加载成功之后。

官方 UIAbilityContext.setColorMode() 文档甚至明确要求:调用前要确保窗口已经创建,并且 UIAbility 对应页面已经通过 loadContent 完成加载。

七、页面究竟在哪一步“出现”

这个问题需要把“创建窗口”和“加载页面”拆开。

如果代码只有:

onWindowStageCreate(windowStage: window.WindowStage): void {
}

能够确认的是:应用已经进入 WindowStage 创建后的这个生命周期回调。

如果执行:

windowStage.loadContent('pages/Index', ...);

才开始建立:

pages/Index
    ↓
UI 实例
    ↓
关联窗口
    ↓
ArkUI 内容呈现

官方 FAQ 明确说明,WindowStage/Window 通过 loadContent 加载页面、创建 UI 实例,并将页面内容渲染到关联窗口。

因此从应用代码的职责边界看,页面出现的关键分界线是 loadContent(),不是 onWindowStageCreate() 本身。

这里还要避免另一个误区:不要把 loadContent 的成功回调简单等价成“屏幕上的最后一个像素已经完成物理显示”的通用性能时间点。

官方另外提供了 reportDrawnCompleted() 用于通知系统 UIAbility 对应窗口内容已经绘制完成,并且官方示例同样是在 loadContent() 成功之后处理这类逻辑。

因此学习 onWindowStageCreate() 时,先把边界理解成:

WindowStage 创建
≠
页面加载
≠
把某个回调直接当成首帧性能指标

这三个概念不要揉成一个。

八、再做一个失败实验

为了理解回调,可以临时把页面路径改成项目中不存在的页面,例如:

windowStage.loadContent('pages/NotExists', (err) => {
  if (err.code) {
    console.error(
      `loadContent failed, code=${err.code}, message=${err.message}`
    );
    return;
  }

  console.info('loadContent success');
});

这个实验的目的不是记某个固定错误码,而是验证一件事:

WindowStage 创建成功与页面内容加载成功,是两个不同阶段。

所以排查“应用启动了但业务首页没有正常出现”时,不要只确认 onWindowStageCreate() 有没有进入。还要继续确认 loadContent() 是否执行、页面路径是否正确,以及失败回调是否已经返回错误。

由于本文没有在 HarmonyOS 工程中实际编译和运行上述修改版代码,这里不声称具体设备一定返回某个固定错误码;实际项目应以目标 HarmonyOS 版本和设备上的运行结果为准。

九、几个最容易理解错的地方

第一个误区是把 onWindowStageCreate() 当成 ArkUI 页面生命周期。它属于 UIAbility 与 WindowStage 这一层,Index.ets 的组件生命周期是另一层概念。

第二个误区是认为 WindowStage 创建之后首页自然存在。官方架构恰恰把应用组件管理、窗口管理和 UI 内容组织拆开了;loadContent() 才承担页面内容进入窗口的关键工作。

第三个误区是只写:

windowStage.loadContent('pages/Index', () => {
});

却完全忽略错误结果。页面路径或加载过程出现问题时,这会把最有价值的定位信息直接丢掉。

第四个误区是把所有初始化都堆在 loadContent() 前面。是否能够这样做,要看具体 API 的前置条件。有些操作明确要求页面加载完成后执行,不能仅凭“现在已经进入 onWindowStageCreate()”判断条件满足。

十、实际项目中怎么排查

如果首页启动阶段出现异常,可以按照一条很短的链路检查:

HarmonyOS / API 版本
        ↓
是否为 Stage 模型 UIAbility
        ↓
onWindowStageCreate 是否进入
        ↓
是否拿到当前 WindowStage
        ↓
是否调用 loadContent
        ↓
页面路径是否正确
        ↓
loadContent 的 err.code 是否为成功状态
        ↓
再检查页面自身 ArkUI 逻辑

这个顺序的好处是先区分“Ability/窗口层问题”和“页面层问题”。

如果 loadContent() 本身就失败了,此时直接去排查 Index.ets 里面某个 Text、Column 或状态变量,方向往往已经偏了。

反过来,如果 loadContent() 已成功,再进入页面组件自身的状态、布局和业务逻辑排查,会更清晰。

开发经验总结

onWindowStageCreate() 本身并不负责“生成首页”。它告诉应用:当前 UIAbility 对应的 WindowStage 已经创建,可以开始处理窗口以及 UI 内容加载。

windowStage 也不是页面。WindowStage 持有并管理窗口,主窗口为 ArkUI 提供绘制区域;页面内容则通过 loadContent() 加载到关联窗口。

所以看到默认模板:

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    // ...
  });
}

可以把它翻译成一句更接近真实职责的话:

“窗口舞台准备好了,现在把 Index 页面装进去。”

这几行代码看起来只是模板,但把这层关系弄清楚以后,再去理解主窗口、子窗口、UIContext、沉浸式窗口甚至多窗口场景,会顺很多。

如果正在学习 Stage 模型,可以自己做一次最小实验:先删除 loadContent(),再恢复它,最后故意传入一个不存在的页面路径。三个状态对照起来,比单纯记住生命周期顺序更容易看清 onWindowStageCreate() 到底负责什么。

❤️ 如果本文帮到了你…

  • 请点个赞,让我知道你还在坚持阅读技术长文!
  • 请收藏本文,因为你以后一定还会用上!
  • 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐