为什么需要 Stage 模型

早期的 FA 模型把页面、业务逻辑与能力入口揉在同一个 Ability 里,应用规模变大后,模块边界模糊、内存与进程管理能力弱的问题开始暴露。Stage 模型重新划分了职责:Ability 只负责“能力”,窗口由 WindowStage 管理,模块由 Module 组织,进程由系统按能力类型调度。

这套划分带来三个实际好处:模块可以独立编译与按需加载;同一应用的不同能力可以运行在不同进程,互不影响;应用的状态管理有明确的层级,便于系统做资源回收。

Stage 模型的核心是“把一个大对象拆成可独立管理的单元”,这与后端服务拆分是同一个思路。

理解它最好的方式是沿着一次启动流程走一遍:AbilityStage 创建 → UIAbility 创建 → 加载页面 → WindowStage 创建 → 页面 onPageShow。

UIAbility:页面能力的载体

UIAbility 是包含界面入口的应用组件,一个应用可以有多个。每个 UIAbility 实例都有一份独立的状态,系统会调用它的生命周期回调。

UIAbility 的四个关键回调
  • onCreate实例创建时调用一次,适合做初始化(读取持久化数据、注册监听)。
  • onWindowStageCreate窗口舞台创建后调用,在这里加载页面内容。
  • onForeground / onBackground应用切到前台 / 退到后台,适合暂停与恢复动画、计时器。
  • onWindowStageDestroy / onDestroy窗口销毁与实例销毁,用来释放资源。
Stage 模型示意:三层悬浮圆角平台构成分层架构,右侧为分段的环形生命周期指示器
分层架构与生命周期:能力、窗口与页面各司其职,生命周期把它们的创建与销毁串成一条链。

一个常见误解是“所有初始化都写在 onCreate 里”。实际上与界面相关的初始化(如给页面注入依赖)放在 onWindowStageCreate 更合适,因为此时窗口已经存在。

WindowStage 与窗口生命周期

WindowStage 是窗口舞台,负责承载应用窗口。你可以把它理解为“应用与显示系统的中间层”:它管理主窗口的创建、前后台切换与销毁,页面的实际内容通过 windowStage.loadContent 加载进来。

import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'EntryAbility', 'onCreate, want=%{public}s', want.abilityName);
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0000, 'EntryAbility', 'onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'EntryAbility', 'loadContent 失败: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(0x0000, 'EntryAbility', 'loadContent 成功');
    });
  }

  onBackground(): void {
    hilog.info(0x0000, 'EntryAbility', 'onBackground');   // 适合暂停计时器、释放相机
  }

  onWindowStageDestroy(): void {
    hilog.info(0x0000, 'EntryAbility', 'onWindowStageDestroy');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'EntryAbility', 'onDestroy');
  }
}

这段代码是标准的 UIAbility 骨架。注意 loadContent 的回调要处理错误:找不到页面或资源缺失时,错误会在这里返回,而不是让应用静默白屏。

应用的三个生命周期阶段

从应用整体看,生命周期分为三个阶段,理解它们才能正确管理资源。

阶段时机该做什么
前台(Foreground)界面可见、可交互启动动画、恢复轮询、刷新数据
后台(Background)界面不可见但仍存活暂停耗时任务、保存草稿、释放相机与定位
销毁(Destroy)实例被回收注销监听、关闭数据库、释放一切持有资源

最容易出问题的是“退到后台还继续跑”:计时器不停、轮询不断、相机不释放,既耗电又可能被系统直接杀掉。正确做法是在 onBackground 中暂停,在 onForeground 中恢复,并把必要的收尾逻辑放在 onDestroy。

还要注意页面级生命周期与 Ability 级生命周期是两套东西:页面的 aboutToAppear / onPageShow / onPageHide / aboutToDisappear 属于组件层,Ability 的四个回调属于能力层。两者会被交替触发,写代码前先想清楚要挂在哪一层。

页面路由与页面栈管理

Stage 模型下,页面跳转通过路由器完成,页面栈由系统维护。每次 push 都会把新页面压栈,返回时弹栈;replace 则替换当前页面,不增加栈深度。

import { router } from '@kit.ArkUI';

// 跳转到详情页并传参
router.pushUrl({
  url: 'pages/DetailPage',
  params: { id: 1024, from: 'list' }
}).catch((e: Error) => {
  console.error(`跳转失败: ${e.message}`);
});

// 在目标页面读取参数
const params = router.getParams() as Record<string, Object>;
const id = params?.['id'] as number;

// 返回上一页
router.back();

// 替换当前页(登录成功进入主页时常用)
router.replaceUrl({ url: 'pages/Home' });

三个实践要点:跳转要 catch 异常(页面未注册会失败);参数用对象传递,别拼在 URL 里;页面栈不要太深,超过三层体验就会变差,能用 Tab 或分段视图的就不该做成多层跳转。

AbilityStage、ExtensionAbility 与进程模型

AbilityStage 是模块级的入口:在 HAP 首次加载时创建,适合做模块级初始化,比如注册模块内的全局服务、初始化模块级的数据库连接。它不是应用级单例,多模块应用会有多个 AbilityStage。

ExtensionAbility 用于非界面能力:后台任务、数据共享、服务卡片、输入法等。它们没有界面,但同样有生命周期,且可能运行在独立进程中,因此不能直接共享内存状态,需要通过跨进程通信或数据共享机制协作。

进程方面,同一应用的不同能力可能被系统调度到不同进程。这意味着:静态变量不一定全局唯一,文件与数据库访问要考虑并发,跨能力通信必须走正式通道。把“不假设同进程”当成默认前提,能避免很多偶发的诡异问题。