Harmony os——UIAbility 组件启动模式彻底搞懂(singleton / multiton / specified)
这篇是我结合官方文档,按自己的理解重新整理的一篇笔记,方便以后写 HarmonyOS 博客用。 主题只有一个:UIAbility 有哪几种启动模式,各自适合什么业务场景,怎么配置,生命周期有什么区别。
1. 为什么要关心“启动模式”?
在 Stage 模型里,UIAbility 是系统调度的基本单元,对应任务视图里的一个「任务卡片」。 同一个 UIAbility:
- 可以只存在 一个实例(比如“首页”)
- 也可以存在 多个实例(比如“一个文档开多份”、“多个聊天窗口”)
- 甚至可以按 某个业务 Key 精准匹配到某个实例(比如“同一个文档不重复创建新窗口”)
这些行为,靠的就是 launchType(启动模式) :
singleton:单实例模式(默认)multiton:多实例模式(以前叫standard)specified:指定实例模式(带 Key 匹配)
2. 三种启动模式总览
先给一个“脑图式”总结:
-
singleton(单实例)
- 同一个 UIAbility,进程里始终只有一个实例
- 再次
startAbility()→ 不新建实例,只走onNewWant() - 最近任务列表里只看到 一个任务卡片
-
multiton(多实例)
- 每次
startAbility()→ 新建一个实例 - 可以开多个同类型 UIAbility(类似“多个窗口”)
- 最近任务列表里能看到多个同名任务
- 每次
-
specified(指定实例)
- 启动时通过
Want.parameters传一个 instanceKey - 系统先走
AbilityStage.onAcceptWant()获取 实例标识 - 如果匹配到已存在实例 → 唤醒并
onNewWant() - 如果没匹配到 → 创建新实例(走
onCreate()+onWindowStageCreate())
- 启动时通过
3. singleton:单实例模式(默认)
3.1 行为特点
-
一种 UIAbility 类型,在一个应用进程中 只会有一个实例。
-
每次调用
startAbility():- 如果该 UIAbility 还没创建 → 正常创建,走
onCreate() → onWindowStageCreate() → onForeground() - 如果该 UIAbility 已经存在 → 只会触发
onNewWant(),不会再创建新实例。
- 如果该 UIAbility 还没创建 → 正常创建,走
-
最近任务列表里,这个 UIAbility 只出现一张卡片。
官方说明点:
- 如果已有实例正在启动过程中,此时再次
startAbility()启动同一个单实例 UIAbility,会直接报错,错误码16000082。
3.2 配置方式
在 module.json5 里配置 launchType:
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility",
"launchType": "singleton",
// ...
}
]
}
}
不写
launchType的话,默认就是singleton。
3.3 适用场景
比较典型的:
- App 主页 / Tab 容器:只需要一个实例,重复打开就复用。
- 设置页 / 个人中心:不需要开多份,只要一个入口。
- 入口 UIAbility:希望在任务视图中只显示一个任务。
4. multiton:多实例模式
4.1 行为特点
- 每次
startAbility()都会创建一个新的 UIAbility 实例。 - 生命周期每次都是完整跑一遍:
onCreate() → onWindowStageCreate() → onForeground() ... - 最近任务列表里,可以看到多个同类型 UIAbility 的任务卡片。
standard是以前的叫法,现在统一叫multiton,效果一样。
4.2 配置方式
{
"module": {
// ...
"abilities": [
{
"name": "ChatAbility",
"launchType": "multiton",
// ...
}
]
}
}
4.3 适用场景
适合那种“天然多窗口”的业务:
- 聊天类:多个会话窗口 各开一份 UIAbility。
- 编辑类:多个草稿 / 草图 /任务 并行编辑。
- 工具类:需要同时打开多个独立任务界面。
缺点也很明显:实例越多,内存越吃紧,需要控制数量和回收。
5. specified:指定实例模式(带 Key 匹配)
这个模式是三种里最“聪明”的一种,适合类似“文档 / 项目”这类有明确唯一标识的业务。
5.1 核心思想
给每个 UIAbility 实例绑定一个 Key,这个 Key 由业务自己定义,比如:
- 文档路径
- 项目 ID
- 会话 ID
启动时流程大致是这样:
-
调用方(比如
EntryAbility)用startAbility()启动SpecifiedAbility在Want.parameters中放一个 Key:instanceKey。 -
系统在真正拉起目标 UIAbility 前,先调用它所在 HAP 上的
AbilityStage.onAcceptWant():- 由开发者根据
want返回一个实例标识字符串。
- 由开发者根据
-
系统根据这个标识去查:
- 如果已经有一个实例的标识一样 → 唤醒那个实例,走
onNewWant()。 - 如果没有匹配到 → 新建一个实例,走
onCreate()+onWindowStageCreate()。
- 如果已经有一个实例的标识一样 → 唤醒那个实例,走
可以理解为: specified = multiton + “按 Key 重用实例”
5.2 配置:先把 UIAbility 声明成 specified
在被指定实例模式使用的 UIAbility 上,先配置:
{
"module": {
// ...
"abilities": [
{
"name": "SpecifiedFirstAbility",
"launchType": "specified",
// ...
}
]
}
}
5.3 调用方:在 Want 里传入 instanceKey
在 EntryAbility 或某个页面里,通过 startAbility() 启动它,同时传入 instanceKey:
import { common, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG: string = '[Page_StartModel]';
const DOMAIN_NUMBER: number = 0xFF00;
function getInstance(): string {
return 'KEY';
}
@Entry
@Component
struct Page_StartModel {
private KEY_NEW = 'KEY';
build() {
Row() {
Column() {
// 启动 SpecifiedFirstAbility,Key 递增
Button('Open First')
.onClick(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const want: Want = {
deviceId: '',
bundleName: 'com.samples.stagemodelabilitydevelop',
abilityName: 'SpecifiedFirstAbility',
moduleName: 'entry',
parameters: {
instanceKey: this.KEY_NEW
}
};
context.startAbility(want).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting SpecifiedAbility.');
}).catch((err: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG,
`Failed to start SpecifiedAbility. Code is ${err.code}, message is ${err.message}`);
});
this.KEY_NEW = this.KEY_NEW + 'a';
})
// 启动 SpecifiedSecondAbility,Key 固定为 KEY
Button('Open Second')
.onClick(() => {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const want: Want = {
deviceId: '',
bundleName: 'com.samples.stagemodelabilitydevelop',
abilityName: 'SpecifiedSecondAbility',
moduleName: 'entry',
parameters: {
instanceKey: getInstance()
}
};
context.startAbility(want).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting SpecifiedAbility.');
}).catch((err: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG,
`Failed to start SpecifiedAbility. Code is ${err.code}, message is ${err.message}`);
});
})
}
.width('100%')
}
.height('100%')
}
}
可以类比:
- 每个
instanceKey= 一个“文档 / 会话”的 ID- 以后只要带着相同的 Key 启动,就会跑回同一个实例。
5.4 被调用方:在 AbilityStage 中根据 Want 返回“实例标识”
接着,在这个模块对应的 AbilityStage 中,通过 onAcceptWant() 把 Want 转成一个唯一字符串标识:
import { AbilityStage, Want } from '@kit.AbilityKit';
export default class MyAbilityStage extends AbilityStage {
onAcceptWant(want: Want): string {
// 针对 launchType 为 specified 的 UIAbility,返回实例 Key
if (want.abilityName === 'SpecifiedFirstAbility' ||
want.abilityName === 'SpecifiedSecondAbility') {
if (want.parameters) {
// 比如:SpecifiedAbilityInstance_某个Key
return `SpecifiedAbilityInstance_${want.parameters.instanceKey}`;
}
}
// 其他情况的默认标识
return 'MyAbilityStage';
}
}
关键点:
onAcceptWant()返回的字符串决定了“这个 want 要归属到哪个实例”;- 系统内部会用这个字符串当作「UIAbility 实例的身份标识」;
- 标识一样 → 认为是同一个实例 → 只走
onNewWant(); - 标识不同 → 创建新实例。
⚠ DevEco Studio 默认工程不会自动生成
AbilityStage文件,需要自己按步骤新建。
5.5 一个典型场景:文档应用
拿文档编辑 App 举个“完整”例子,会更好理解:
-
打开文件 A → 创建 UIAbility 实例 1,Key =
doc:/A -
在最近任务中滑掉 A → 实例 1 被销毁
-
回到桌面再打开 A → 创建 UIAbility 实例 2,Key 仍为
doc:/A -
打开文件 B → 创建 UIAbility 实例 3,Key =
doc:/B -
回到桌面,再次打开 A:
- 系统根据
instanceKey = doc:/A调用onAcceptWant() - 匹配到之前的 UIAbility 实例 2
- 于是直接把实例 2 拉到前台(走
onNewWant()),而不是新建实例
- 系统根据
这个模式刚好解决了:
- “新建文档要开新窗口”
- “再打开旧文档时,回到原来的编辑界面”的需求 → 非常适合编辑器 / IDE / 文档类应用。
6. 生命周期和启动模式的关系补充一下
-
singleton / specified(匹配已有实例)
-
再次
startAbility()时:- 不会再走
onCreate()、onWindowStageCreate() - 只会触发
onNewWant()
- 不会再走
-
-
multiton / specified(没有匹配到实例)
-
每一次启动都是:
onCreate()onWindowStageCreate()onForeground()
-
7. 我自己的使用习惯小总结
如果我来设计一个 App,常规会这么用:
-
主页 / Tab 容器:
singleton -
单独的一个功能窗口(设置、关于、搜索结果) :多数也是
singleton -
可同时打开多个任务的模块(文档 / 会话 / 项目) :
- 如果不需要区分“旧文档 / 新文档”,只要多窗口 →
multiton - 需要“按文档 ID 重用窗口” →
specified+instanceKey
- 如果不需要区分“旧文档 / 新文档”,只要多窗口 →
-
涉及“按 Key 复用实例”的,一律考虑
specified,配合AbilityStage.onAcceptWant()做精细控制