在这里插入图片描述

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

前言

刚接触 HarmonyOS 的 Ability 跳转时,Want 很容易被理解成“页面跳转参数”。这个理解不能说完全错,但范围太窄。

华为官方对 Want 的定义更准确:**Want 是对象间信息传递的载体,可以用于应用组件间的信息传递。**在启动 UIAbility 这个场景里,它既可以描述“我要去哪里”,也可以携带“我要带什么数据过去”。官方文档给出的典型字段正是 bundleName、abilityName 和 parameters。

这次不把问题铺开,只做一个最小实验:

A Ability → 构造 Want → 启动 B Ability → B 从 Want 中取出 parameters。

把这条链路跑通,Want 的角色基本就清楚了。

HarmonyOS 7 已对应 API 26,官方升级适配文档明确给出了这一版本关系;本文使用的 UIAbilityContext.startAbility() 等基础能力并不是 API 26 才新增的接口,当前 UIAbilityContext 文档说明该模块首批接口从 API version 9 开始支持,因此 HarmonyOS 7 / API 26 可以继续使用。

一、先别把 Want 理解成“跳转函数”

先看这次实验里最重要的一段数据:

let want: Want = {
  bundleName: 'com.example.wantdemo',
  abilityName: 'BAbility',
  parameters: {
    from: 'AAbility',
    message: 'Hello Want'
  }
};

如果暂时不考虑调用 startAbility(),单独看这个对象,其实已经能看出 Want 的职责。

字段这次实验中的作用
bundleName指明目标 Ability 所属应用的 Bundle 名称
abilityName指明目标 Ability
parameters携带开发者需要传递的键值对数据

官方 Want API 文档说明,当 bundleName 和 abilityName 同时指定时,Want 可以直接匹配指定的 Ability;parameters 则用于保存开发者传入的键值对。

所以更合适的理解是:

Want 本身负责描述目标和携带信息;真正执行 UIAbility 启动动作的是对应 Context 提供的启动接口。

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

二、先把这次实验的官方规则确认清楚

本文按 Stage 模型 + ArkTS + Ability Kit 来组织最小示例。

当前官方 UIAbilityContext 文档说明,每个 UIAbility 实例化时,系统都会创建对应的 UIAbilityContext;开发者可以利用它拉起其他 UIAbility。该模块首批接口从 API version 9 开始支持,并且仅可用于 Stage 模型。startAbility(want) 接收的核心参数就是 Want。

本文实验边界如下:

  • 开发背景:HarmonyOS 7 / API 26;
  • 应用模型:Stage 模型;
  • Kit:Ability Kit;
  • 组件:两个 UIAbility,分别记为 A Ability 和 B Ability;
  • 场景:同一应用内,从前台 A Ability 显式启动 B Ability;
  • 数据:只传两个字符串参数;
  • 权限:这个最小场景不额外增加 requestPermissions;
  • 不涉及跨设备、后台拉起以及系统应用专属能力。

官方当前文档中,UIAbilityContext.startAbility() 的系统能力为 SystemCapability.Ability.AbilityRuntime.Core,并明确要求在主线程调用。文档同时列出了目标 Ability 不存在、组件类型错误、不可见组件、Context 不存在等错误情况。

这里没有必要为了“像完整项目”而塞进额外权限和配置。最小实验只验证 Want 本身。

三、搭一个最小的 A / B Ability

假设应用的 Bundle 名称为:

com.example.wantdemo

Module 中准备两个 UIAbility:

entry
├── src/main/ets/
│   ├── aability/
│   │   └── AAbility.ets
│   ├── bability/
│   │   └── BAbility.ets
│   └── pages/
│       ├── Index.ets
│       └── BPage.ets
└── src/main/module.json5

module.json5 的 abilities 中需要存在对应的 Ability 配置。官方文档中的 UIAbility 配置示例使用 name 指定 Ability 名称,使用 srcEntry 指向实现文件。

下面只保留与实验直接相关的结构:

{
  "module": {
    // 其他已有配置保持不变
    "abilities": [
      {
        "name": "AAbility",
        "srcEntry": "./ets/aability/AAbility.ets",
        "exported": true
      },
      {
        "name": "BAbility",
        "srcEntry": "./ets/bability/BAbility.ets",
        "exported": false
      }
    ]
  }
}

这里真正需要记住的是:Want 中的 abilityName 要和目标 UIAbility 的名称对应。

如果项目本身已经有入口 EntryAbility,完全没必要为了文章把它改名成 AAbility。实际迁移时可以把现有入口 Ability 当作 A,只额外增加 B Ability。

四、A 页面:构造一个最小显式 Want

A 页只放一个按钮。

点击按钮以后获取 UIAbilityContext,创建 Want,然后启动 B Ability:

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

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Text('A Ability')
        .fontSize(28)

      Button('携带参数打开 B Ability')
        .onClick(() => {
          const context =
            this.getUIContext().getHostContext() as common.UIAbilityContext;

          const want: Want = {
            bundleName: 'com.example.wantdemo',
            abilityName: 'BAbility',
            parameters: {
              from: 'AAbility',
              message: 'Hello Want'
            }
          };

          try {
            context.startAbility(want)
              .catch((err: BusinessError) => {
                console.error(
                  `startAbility failed, code=${err.code}, message=${err.message}`
                );
              });
          } catch (err) {
            const error = err as BusinessError;
            console.error(
              `startAbility failed, code=${error.code}, message=${error.message}`
            );
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

当前官方 UIAbilityContext API 示例已经采用:

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

并通过 UIAbilityContext.startAbility(want) 启动目标 UIAbility;Promise 形式的接口返回 Promise<void>。

这段代码真正需要关注的不是按钮,而是下面三个字段:

const want: Want = {
  bundleName: 'com.example.wantdemo',
  abilityName: 'BAbility',
  parameters: {
    from: 'AAbility',
    message: 'Hello Want'
  }
};

其中前两个字段回答“去哪里”,parameters 回答“带什么”。

这就是本文最核心的实验。

五、B Ability:参数不是去页面路由里找,而是从 Want 里拿

接下来实现 B Ability。

官方资料中已经给出了直接在 UIAbility 的 onCreate(want: Want) 中读取 want.parameters 的用法;对于已经存在的 UIAbility 实例再次收到新的 Want,官方示例还会在 onNewWant(want: Want) 中解析参数。

因此 B Ability 可以写成:

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

export default class BAbility extends UIAbility {
  private parseWant(want: Want): void {
    const from = want.parameters?.['from'] as string;
    const message = want.parameters?.['message'] as string;

    console.info(`[BAbility] from=${from}`);
    console.info(`[BAbility] message=${message}`);
  }

  onCreate(
    want: Want,
    launchParam: AbilityConstant.LaunchParam
  ): void {
    this.parseWant(want);
  }

  onNewWant(
    want: Want,
    launchParam: AbilityConstant.LaunchParam
  ): void {
    this.parseWant(want);
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/BPage');
  }
}

B 页面本身甚至不需要承担 Want 解析职责。为了让实验尽可能干净,可以只显示一个静态页面:

@Entry
@Component
struct BPage {
  build() {
    Column({ space: 16 }) {
      Text('B Ability')
        .fontSize(28)

      Text('Want 参数已由 BAbility 接收,可查看日志')
        .fontSize(16)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

按照这个结构,实验观察点非常明确:

A 页面点击按钮
        ↓
构造 Want
        ↓
bundleName + abilityName 指向 BAbility
        ↓
parameters 携带 from / message
        ↓
BAbility 收到 Want
        ↓
读取 want.parameters

六、为什么 B 里同时写 onCreate 和 onNewWant

这个地方比较容易理解错。

如果只为了第一次启动 B Ability,下面这段已经足够说明 Want 如何进入目标组件:

onCreate(want: Want): void {
  const message = want.parameters?.['message'] as string;
}

但实际开发不能形成“Want 永远只在 onCreate 里拿”的印象。

官方最新业务示例明确展示了两种接收位置:UIAbility 实例创建时在 onCreate 中解析 Want;已经启动的 UIAbility 实例再次被拉起时,可以在 onNewWant 中接收并解析新的 Want。

因此更稳妥的写法是把解析逻辑抽成:

private parseWant(want: Want): void

然后两个入口复用。

这样关注点仍然是 Want,没有把文章带到 UIAbility 启动模式的细节里。

七、bundleName、abilityName、parameters 分别不要搞混

1. bundleName 不是随便起的业务字符串

bundleName 用来标识目标 Ability 所属应用。

如果示例代码里写的是:

bundleName: 'com.example.wantdemo'

那么迁移到自己的工程时就必须替换成真实应用对应的 Bundle 名称。

把网上 Demo 的 com.example.xxx 原样复制过去,是最直接的一类目标匹配错误。

2. abilityName 指的是目标 Ability

本文目标组件叫:

BAbility

所以 Want 中写:

abilityName: 'BAbility'

官方 Want 文档说明,同时给出 bundleName 和 abilityName 时,可以直接匹配指定 Ability。

因此排查 16000001 一类“指定 Ability 不存在”的问题时,除了看调用代码,也要回头核对 module.json5 中 Ability 的声明。startAbility() 当前 API 文档明确列出了 16000001、16000002、16000004 等启动错误。

3. parameters 才是我们自己的业务数据

本文放进去的是:

parameters: {
  from: 'AAbility',
  message: 'Hello Want'
}

B 端按照同一个 key 获取:

const from = want.parameters?.['from'] as string;
const message = want.parameters?.['message'] as string;

这里建议业务项目自己统一管理 key,不要在多个 Ability 中到处手写不同字符串。

比如至少可以集中定义:

export class WantKeys {
  static readonly FROM: string = 'from';
  static readonly MESSAGE: string = 'message';
}

这样可以减少发送端写 message、接收端却误写成 msg 之类的问题。

八、几个容易出现的理解偏差

Want 不等于页面路由参数

这次确实拿 Want 做了“A → B 页面”的参数传递,但 Want 面向的是对象和应用组件之间的信息传递,不应该简单等同于 ArkUI 页面内部的普通路由参数。官方对 Want 的定义本身就比“页面参数”更宽。

startAbility 成功,不代表你应该从它那里等一个业务返回值

本文只讨论单向过程:

A → Want → B

A 把目标和参数放入 Want,B 接收。

把这个单向模型先理解清楚,比一开始同时塞入更多通信概念更容易看明白 Want 本身。

parameters 的 key 是双方约定

系统负责把 Want 作为载体送到目标组件,但 from、message 这些业务 key 是开发者自己定义的。

因此:

parameters: {
  message: 'Hello Want'
}

和:

want.parameters?.['message']

实际上构成了一份最小的数据协议。

九、显式 Want 和隐式 Want,是两条不同路线

到这里再看“显式”和“隐式”,就比较容易理解了。

本文使用的是典型的显式 Want:

const want: Want = {
  bundleName: 'com.example.wantdemo',
  abilityName: 'BAbility'
};

调用方已经知道目标是谁,直接指定应用和 Ability。

而隐式 Want 的思路不是“少写一个 abilityName”这么简单。官方资料将显式 Want 与隐式 Want 单独作为匹配规则进行说明;隐式场景可以涉及 action、entities、uri、type 等信息,并与目标组件声明的能力进行匹配。

所以可以把两条路线简单记成:

显式 Want:
我知道要找谁
→ bundleName + abilityName
→ 直接指向目标组件

隐式 Want:
我描述自己想做什么
→ action / entities / uri / type 等匹配信息
→ 系统按照规则寻找合适目标

这里还有一个当前版本需要特别留意的变化:华为官方最新文档已经将“常见 action 与 entities”章节标记为不推荐使用,并说明由于 action/entity 被泛化使用带来的匹配问题,后续会逐步废弃非必要 action/entity,建议按官方当前推荐方式选择对应能力。

因此本文只把显式和隐式的概念边界说明白,不拿传统 action/entity 示例继续扩展一个新的 Demo。

十、这个最小实验出问题时,按什么顺序查

如果 A 点击以后没有按预期进入 B,可以先别动页面代码,按下面顺序排查。

第一步,看目标信息。

确认:

bundleName
abilityName

是否确实指向当前目标。

第二步,看 module.json5。

确认 BAbility 已经存在于 abilities 配置中,name 与 Want 中使用的名称一致,srcEntry 指向实际文件。官方配置示例明确采用 name 和 srcEntry 描述 UIAbility。

第三步,看 startAbility 错误。

不要只写:

context.startAbility(want);

然后发现“没反应”。

保留错误处理,重点看错误码。官方文档当前列出的错误包括目标 Ability 不存在、Ability 类型错误、不可启动不可见组件、Context 不存在、无匹配 Ability 等。

第四步,看 B 的接收位置。

第一次创建实例时关注:

onCreate(want)

已有实例再次收到 Want 的场景还要关注:

onNewWant(want)

官方现有示例就是同时在这两个回调中解析 Want。

第五步,再检查 parameters。

确认发送端:

message

和接收端:

want.parameters?.['message']

使用的是同一个 key。

这套排查顺序的好处是,能够把“目标没找到”和“目标找到了但业务参数没取对”分开处理。

开发经验总结

Want 本身并不复杂,真正容易混淆的是它和“启动动作”“页面路由”“业务参数”之间的关系。

通过这个 A / B Ability 最小实验,可以留下四个比较稳定的认识:

第一,Want 是信息载体。 官方定义不是“跳转 API”,而是对象和应用组件之间的信息传递载体。

第二,显式 Want 同时解决两个问题:目标是谁、带什么过去。 bundleName 和 abilityName 描述目标,parameters 承载业务数据。

第三,B 获取参数的入口是收到的 Want。 UIAbility 创建时可以从 onCreate(want) 获取;已有实例再次收到新的 Want 时还要关注 onNewWant(want)。

第四,显式 Want 和隐式 Want 不要混成一种写法。 前者明确指定目标,后者走匹配机制;而且官方对传统 action/entities 的使用已经给出了新的“不推荐使用”提示。

❤️ 如果本文帮到了你…

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

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

更多推荐