Stage 模型 vs Android 运行时心智:为什么 UIAbility 不是 Activity 的同名替换

先抛结论(用 Android 做对照,但主角始终是 Stage 模型本身):

1) Stage 模型的主线不是“回调名”,而是“舞台对象”AbilityStage 管组件、WindowStage 管窗口、UIContext 是 UI 侧的上下文入口。 2) UIAbility 可以类比 Activity,但不要当成同名替换:真正要对齐的是“容器 + 栈 + 窗口/上下文”的组织方式。 3) 学习顺序更像一章小书:先理解对象分层,再理解生命周期的“阶段”,最后再落到路由(Want/skills)与启动模式(launchType)。

为了让你一眼看清楚差异,我先把两边的“对象分层”放在一张图里(后面所有细节都围绕这张图展开)。

Android 运行时与 HarmonyOS Stage 模型的对象分层对照

先把 Stage 模型讲清楚:它到底在解决什么问题?

很多对比文章会从“名字对照表”开始:

  • Activity ↔ UIAbility
  • Intent ↔ Want
  • intent-filter ↔ skills

这些对照可以当作索引,但它解释不了 Stage 模型的关键差异:Stage 把“组件管理”和“窗口管理”显式分层了。 如果你不先把这条主线抓住,后面看生命周期、路由、上下文都会觉得像“记名词”。

下面我会先按 Stage 模型自己的分层把它讲清楚,再用 Android 的对象边界做对照,帮助你把熟悉的经验迁移到正确的位置。

1) 为什么我按 Activity 的写法迁 UIAbility,状态就乱了? 2) 为什么路由规则看起来都配了,但隐式拉起总是“有时行,有时不行”? 3) 为什么我拿到一个 context 以后,窗口/资源/权限的访问方式完全不一样?

根因通常不是 API,而是你对“运行时边界”的默认假设不同。

接下来我用同一套“分层语言”分别把 Android 与 Stage 模型讲一遍,然后给出一份可执行的映射与迁移 checklist。

Android 运行时(对照用):你习惯的边界大概长这样

Android 开发者对 UI 的运行时心智,通常会落在这几个“稳定抓手”上:

  • 进程/应用:Application 初始化、全局单例、进程存活与回收
  • 任务栈/返回链路:Task / Back Stack 决定返回键与“回到哪一屏”
  • 容器(Activity):一屏一个 Activity 或单 Activity 多页面(Fragment/Compose Nav)
  • 窗口/渲染:Window / View / Compose 层做 UI 布局与绘制
  • 路由契约Intent + intent-filter(声明 + 请求,系统负责匹配与分发)

你在 Android 里遇到“返回键不对/页面实例不对/被重复创建”,一般会先从 launchMode、Task 行为、Manifest 声明去定位;遇到“跳转到错的组件”,会回到 intent-filter 的 action/category/data 规则去看匹配。

这套排障路径之所以稳定,是因为 Android 的对象边界相对固定:容器/栈/窗口在大多数应用里都很好对齐。

Stage 模型:AbilityStage / WindowStage / UIContext 这条主线

Stage 模型这个名字本身就暗示了它的主线:用“舞台对象”把职责拆开

  • AbilityStage:组件管理的“舞台”
  • WindowStage:窗口管理的“舞台”
  • UIAbility:作为能力入口的 UI 容器(在 Stage 模型中是主角之一)
  • UIContext:ArkUI 侧的 UI 上下文入口(你在组件里经常通过 getUIContext() 拿到它)

如果你只把 UIAbility 看成 Activity,会天然忽略两个很重要的事实(也就是 Stage 模型“分层”的落点):

1) UIAbility 的生命周期里有一段明确的“窗口舞台创建”阶段(比如 onWindowStageCreate)。 2) 你在 ArkUI 组件里拿到的往往不是“AbilityContext”,而是 UIContext,再通过 getHostContext() 回到宿主的 UIAbilityContext

这不是文字游戏,它会直接影响你的代码落点:窗口相关的事,应该放在 WindowStage 创建阶段做;UI 组件拿 window/资源时,要从 UIContext 回到宿主上下文。

举个很典型、也很“工程味”的例子:在 ArkUI 组件内拿到主窗口(你一眼就能看出它的层级关系):

import { common } from '@kit.AbilityKit';

const mainWindow = (this.getUIContext().getHostContext() as common.UIAbilityContext)
  .windowStage
  .getMainWindowSync();

你看到了:UIContext → HostContext(UIAbilityContext) → windowStage → mainWindow。这条链路把“UI 组件”和“窗口舞台”连接了起来。

对照映射:把对象对齐到同一张“边界地图”上

下面这张表我刻意不按“名字相似度”排,而是按“迁移时你会写错的位置”排:你要先知道它在运行时的哪一层,才知道应该把代码放在哪。

你在设计时关心的边界 Android(你熟悉的对象) Stage 模型(对应对象) 迁移落点
应用/进程级初始化 Application HAP/应用级对象(入口配置在 app.json5 / module.json5 全局初始化 vs Ability 内初始化分清
组件管理边界 Manifest 声明 + AMS/系统调度 AbilityStage “组件管理”不要混进窗口逻辑
UI 容器边界 Activity UIAbility 把它当“能力入口 + UI 容器”,别当纯回调集合
窗口管理边界 Window / WindowManager WindowStage(及主窗口) 窗口相关逻辑在 WindowStage 阶段收口
UI 上下文入口 Context(Application/Activity) UIContext(再 getHostContext() UI 组件里先 UIContext,再回宿主上下文
路由契约(声明 + 请求) intent-filter + Intent skills + Want 先做“最小规则可验证”,再扩展

你不需要背表。你只要记住一条:Stage 模型里,窗口相关逻辑优先挂在 WindowStage 这条线上;UI 侧上下文优先从 UIContext 走回宿主上下文。 这条记住,后面的生命周期与路由会自然落到正确位置。

启动模式:Stage 的 `launchType` 应该怎么理解?

Android 里,Activity 是否“复用实例”通常用 launchMode(以及任务栈相关 flag)去控制。这里我只把它当作对照背景。

在 Stage 模型里,UIAbility 的启动模式更靠近配置侧:你会在 module.json5 里看到 launchType 这个字段:

  • multiton:多实例(每次启动创建新实例)
  • singleton:单实例(仅第一次启动创建新实例)
  • specified:指定实例(运行时由开发者决定是否创建新实例)
  • standardmultiton 的曾用名(等价)

学习/设计 Stage 模型时也不要用“单例/多例”的中文直觉直接拍脑袋,而是回到这条判断链:

1) 这个 UIAbility 是不是对外入口?(对外入口通常更倾向稳定的实例策略) 2) 这个能力是否允许并行存在多个任务实例?(比如文档/编辑器这类) 3) 你的返回链路是“能力级”还是“页面级”?(能力级多实例会直接影响返回体验)

你只要把启动模式的判断放回到“栈模型 + 入口语义”,就不容易写出“看起来能跑,但返回键永远不对”的版本。

生命周期:先抓住“窗口舞台”阶段,再去看回调名

我不准备在这里做完整的回调对照表(那会把读者带回“翻译器”思路)。但有两条实践建议是值得记住的:

1) Android 里你常把 UI 初始化放在 onCreate,Stage 模型里要特别重视 WindowStage 创建阶段。 2) 如果你在 UI 组件里需要 window/资源/权限相关对象,优先从 UIContext → HostContext 回到 UIAbilityContext。

如果你需要一个“视觉锚点”,我们系列前面已经用过两张生命周期图(你可以把它当成排障时的快速参考):

  • Android Activity lifecycle
  • Android Activity lifecycle

  • HarmonyOS UIAbility lifecycle
  • HarmonyOS UIAbility lifecycle

落地 checklist:把“对象分层”变成可执行步骤

最后给一份我自己在 Stage 项目里会用的 checklist。它比“回调对照表”更能帮你把概念落到代码位置上:

1) 先画对象分层:这段逻辑属于“组件管理/窗口管理/UI 渲染/路由契约”的哪一层? 2) 再定容器粒度:这是 Page 级(页面内切换)还是 UIAbility 级(能力入口)? 3) 启动模式先定再写:先把 launchType 选对,否则后面所有返回/复用都是补丁。 4) 窗口相关逻辑收口到 WindowStage:别分散在到处都能触发的回调里。 5) 路由契约按“声明 + 请求”写:skills 是声明,Want 是请求;先最小集合可验证,再扩展。 6) UI 组件拿能力对象走 UIContext 链路getUIContext()getHostContext() →(必要时)windowStage / resourceManager 等。

当你按这 6 步走完,再去做 Intent/Want、intent-filter/skills 的字段对照,你会发现对照表反而更容易写对——因为你知道每个字段应该服务哪个“边界”。

参考素材(精选)

  • Android 官方:Intents and Intent Filters
  • https://developer.android.com/guide/components/intents-filters

  • HarmonyOS 官方:ArkUI Stage模型简介
  • https://developer.huawei.com/consumer/cn/arkui/arkui-stage/

  • OpenHarmony 文档:Want overview / Want API reference
  • https://gitee.com/openharmony/docs/blob/master/en/application-dev/application-models/want-overview.md https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis/js-apis-application-want.md

// Kai@CodeHubble

// 观测坐标:Android-HarmonyOS/2026-05-12

上一篇
下一篇