HarmonyOS鸿蒙Next中如何正确处理Ability的多实例并发?比如用户快速双击图标启动两个窗口?

HarmonyOS鸿蒙Next中如何正确处理Ability的多实例并发?比如用户快速双击图标启动两个窗口? 我们的文档编辑 App 支持多窗口,但用户双击桌面图标时,有时会打开两个完全相同的文档实例,导致数据冲突。鸿蒙有没有类似 Android 的 launchMode="singleTask" 机制来控制 Ability 启动模式?

5 回复

针对这个问题:”用户双击桌面图标时,有时会打开两个完全相同的文档实例“
建议你提工单给华为后台。这个其实是系统问题了。

提工单的地址:在线提单 - 华为开发者联盟,请选择正确的问题类型进行提单

当然要是在app内打开一个查看文档页面,那推荐这个”specified启动模式“,具体使用方式看文档。

UIAbility组件启动模式-UIAbility组件-Stage模型应用组件-Stage模型开发指导-Ability Kit(程序框架服务)-应用框架 - 华为HarmonyOS开发者 (huawei.com)

更多关于HarmonyOS鸿蒙Next中如何正确处理Ability的多实例并发?比如用户快速双击图标启动两个窗口?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


如果使用单例模式的话,其实用户体验也不太好。

可以给使用编辑功能的 UIAbility 配置 指定实例 (specified)启动模式,文档类的最适合这个了。

官方文档原文:specified 启动模式为指定实例模式,针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)。

模块级 module.json5 配置:

{
  "module": {
    "name": "entry",
    "type": "entry",
    // 只要有UIAbility为指定实例模式启动模式的时候,这里就不能使用 `UIAbility` ,而要使用 `AbilityStage`;
    "srcEntry": "./ets/abilityStage/CommonAbilityStage.ets",
    "abilities": [
      // 主 UIAbility
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        // ...
        // 标识当前UIAbility组件是否可以被其他应用拉起
        "exported": true,
        // 单实例模式(默认模式,即使没有显式指定)
        "launchType": "singleton",
      },
      // 编辑功能 UIAbility
      {
        "name": "EditDocumentAbility",
        "srcEntry": "./ets/entryability/EditDocumentAbility.ets",
        // ...
        // 标识当前UIAbility组件是否可以被其他应用拉起
        "exported": true,
        // UIAbility指定实例模式(启动时动态设置)
        "launchType": "specified",
      },
    ],
  }
}

原理:

原理图

全部代码就不贴了,其实要贴也是在官方文档给你复制过来。还可能会有遗漏。你看下面参考链接里的指南和官方示例项目吧,里面有实现效果的gif。遇到困难了可以继续提问。看到了会回复的。

关键点在于以下几点:

  1. 给编辑功能的 UIAbility 创建一个 AbilityStage 子类并按照上方代码代码配置 module.json5
  2. 创建一个通用的 Want 构造方法,根据业务(例如使用文档的uri,能代表文档的唯一性的值)给 Want 添加额外参数。
  3. 使用 Want 拉起 UIAbility 的时候,系统会从你编写的 AbilityStage#onAcceptWant() 方法里获取你返回的Key值(使用第2点里的额外参数自行处理)。
  4. 这个Key值就决定了系统要拉起已打开的 UIAbility 实例还是创建新 UIAbility 实例。(每一个实例都是体现在最近任务界面中的一个快照)

参考:

(最后补充一句题外话,这是我踩过的一个坑:如果设置了 指定实例 启动模式之后,那么这个 UIAbility 在使用 Push Kit 并要实现 前台处理通知消息 的时候,是触发不了相关回调的。必须要 单例模式 才可以,如果恰巧有这个需求,需要自己寻求办法处理。这个问题仅影响当前 UIAbility 。)

鸿蒙通过 module.json5 中的 launchType 控制启动行为:

  • "standard"(默认):每次启动创建新实例;
  • "singleton":整个系统只保留一个实例,新请求复用并回调 onNewWant()
  • "multiton":同一用户下允许多实例,但可通过 want 参数区分(如 documentId

在HarmonyOS Next中,Ability的多实例并发通过AbilityStage和WindowStage管理。应用启动时,AbilityStage会创建Ability实例并分配独立的WindowStage。对于快速双击启动多窗口场景,系统会为每个请求创建独立的Ability实例,每个实例拥有自己的UI上下文和窗口栈。通过AbilityContext的startAbility()方法启动时,若未指定单实例模式,系统默认支持多实例。每个实例生命周期独立,由系统统一调度资源分配。

在HarmonyOS Next中,处理Ability的多实例并发有明确机制,与Android的launchMode概念不同,但能实现相同效果。

核心是通过AbilityInfo中的launchType字段和启动参数StartOptions中的windowMode来协同控制。

1. 关键配置:launchTypemodule.json5配置文件的abilities项中,为你的Ability设置launchType

  • standard(默认):多实例模式。每次启动都会创建新实例。这可能导致你描述的“双击打开两个实例”问题。
  • singleton:单实例模式。这是你需要的、类似singleTask的解决方案。系统只存在一个该Ability的实例。如果实例已存在(无论在前台或后台),再次启动时会将其拉到前台并触发onNewWant回调,而不会创建新实例

配置示例:

{
  "module": {
    "abilities": [
      {
        "name": ".MainAbility",
        "launchType": "singleton", // 设置为单实例
        // ... 其他配置
      }
    ]
  }
}

2. 实现多窗口:windowMode 即使设置为singleton,通过配合不同的windowMode,也能实现多窗口效果(如分屏、悬浮窗),但这与创建多个应用实例有本质区别。 在启动Ability时,通过StartOptions设置窗口模式:

import { AbilityConstant, common, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

let want: Want = {
  bundleName: 'com.example.app',
  abilityName: 'MainAbility',
  // ... 其他参数
};
let options: common.StartOptions = {
  windowMode: AbilityConstant.WindowMode.WINDOW_MODE_FLOATING, // 例如启动为悬浮窗
};
this.context.startAbility(want, options).catch((err: BusinessError) => {
  console.error(`Failed to start ability. Code: ${err.code}, message: ${err.message}`);
});

3. 处理数据传递:onNewWantsingleton模式的Ability被再次启动时,会回调onNewWant。这是处理新意图(例如打开不同文档)的关键位置。

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class MainAbility extends UIAbility {
  // ...

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 在此处解析want中的新参数(如文档ID)
    console.info(`New want received: ${JSON.stringify(want)}`);
    // 可以根据参数更新当前窗口内容,而不是开新实例
    this.updateDocument(want.parameters?.docId);
  }

  private updateDocument(docId: string | undefined): void {
    // 实现你的文档更新逻辑
  }
}

针对你问题的直接解决方案:

  1. 将文档编辑Ability的launchType设置为singleton
  2. onNewWant回调中,解析启动参数(例如要打开的文档路径或ID)。
  3. 如果新意图要求打开另一个文档,而你希望支持多文档窗口,则应在onNewWant中通过startAbility并指定windowMode为多窗口模式(如WINDOW_MODE_FLOATING)来启动一个新的文档展示Ability(可配置为多实例)。这通常意味着将“主界面”Ability设为单例,而将具体的“文档编辑”页面或Ability设计为可多实例。

总结: 鸿蒙Next通过launchType: 'singleton'提供了原生单实例控制,有效防止重复启动。多窗口需求需通过windowMode与Ability设计(如分离主控Ability与文档编辑Ability)来配合实现,而非依赖多个相同Ability实例。

回到顶部