在 Agent 时代的应用开发中,一个根本性的变化是用户不再关心由哪个应用完成任务,只关心任务本身是否被正确完成。鸿蒙意图框架(Intents Kit)正是实现这种“以意图为中心”分发的关键基础设施。本文以一个真实可编译的 Demo——「小艺出行」为主线,完整复盘其从零构建到在手机与鸿蒙 PC 双端运行的全过程,重点解析意图声明、执行器实现、UI 回流机制、导航拉起以及常见问题排查等核心技术细节。
一、开发范式的认知升级
传统 App 开发以“应用为中心”,用户需解锁、点击图标、打开应用、再通过多步操作完成任务。而 Agent 模式以“意图为中心”,用户只需说一句“帮我加个明天九点去机场的行程”,系统即完成意图理解、参数抽取并路由给最合适的能力提供方。开发者的核心工作从设计吸引用户的界面,转变为向系统清晰声明:可处理哪些意图、需要哪些参数、执行后返回何种结果。这一转变可归纳为三个层面:从流量获取到意图接入、从界面开发到契约定义、从单点体验到全场景触达。理解这些,再看意图框架 API 的设计逻辑就清晰了。
二、场景选取与 Demo 整体架构
选定出行助手作为演示场景,因为其意图表达天然口语化、参数可清晰结构化、可演示多意图协同(添加、查询、导航),且天然具备跨设备特性。Demo 名为「小艺出行」,纯端侧无后端依赖,包含三个页面(概览、行程、我的)和三个意图(AddTrip、QueryTrips、NavigateTo),采用底部 Tab 切换深色科技风格。工程目录关键结构如下(仅列核心文件):- entry/src/main/
- ├── ets/
- │ ├── entryability/EntryAbility.ets
- │ ├── intents/TripIntentExecutor.ets
- │ ├── model/Trip.ets
- │ ├── model/IntentHandler.ets
- │ ├── model/NavHelper.ets
- │ └── pages/
- │ ├── Index.ets
- │ ├── OverviewTab.ets / TripsTab.ets / ProfileTab.ets
- ├── resources/base/profile/insight_intents.json
- └── module.json5
复制代码
三、核心实现:向系统声明应用能力
这是从 App 到 Agent 转变中最关键的一步。首先编写意图声明文件 `insight_intents.json`,向系统明确三件事:应用具备哪些意图、每个意图的定义、执行所需的参数。以“添加行程”为例:- {
- "intentName": "AddTrip",
- "domain": "TravelManagement",
- "displayName": "添加行程",
- "llmDescription": "当用户想安排出行、添加行程、记录一次出发时使用。可从表达中提取目的地 destination、出行方式 type、时间 when、备注 note。例如「帮我加个明天9点去首都机场的行程」。",
- "keywords": ["加行程", "安排出行", "添加行程", "去", "出发"],
- "parameters": {
- "type": "object",
- "properties": {
- "destination": { "type": "string", "description": "目的地,必填" },
- "type": { "type": "string", "enum": ["飞机", "高铁", "驾车", "打车", "地铁", "步行"] },
- "when": { "type": "string", "description": "出发时间的自然语言描述" },
- "note": { "type": "string", "description": "备注,可选" }
- },
- "required": ["destination"]
- },
- "executor": "ets/intents/TripIntentExecutor.ets"
- }
复制代码 值得关注的是 `llmDescription` 字段——它以自然语言撰写、供大模型理解能力说明,相当于面向 AI 的 prompt。`keywords` 辅助召回,`parameters` 定义结构化契约,由系统大模型将口语表达解析为结构化字段,开发者无需实现 NLP 逻辑。
声明文件需在 `module.json5` 中通过 metadata 注册:- "metadata": [
- { "name": "ohos.ability.intent.metadata", "resource": "$profile:insight_intents" }
- ]
复制代码
四、意图执行器的实现
三个意图共用一个执行器 TripIntentExecutor,按意图名称分派。执行器的关键方法 `onExecuteInUIAbilityForegroundMode` 接收意图名称和参数,执行后返回 `ExecuteResult`,其中 `result.message` 不仅用于 UI 展示,还可被小艺语音播报,因此文案需兼顾“可视”与“可听”。示例核心逻辑:- export default class TripIntentExecutor extends InsightIntentExecutor {
- async onExecuteInUIAbilityForegroundMode(
- name: string,
- param: Record<string, Object>,
- pageLoader: object
- ): Promise<insightIntent.ExecuteResult> {
- let message = '';
- if (name === 'QueryTrips') {
- message = await this.doQuery(str(param['when']));
- } else if (name === 'NavigateTo') {
- const dest = str(param['destination']);
- await openNavigation(this.context as common.UIAbilityContext, dest);
- message = `正在为你拉起地图,规划前往「${dest}」的路线…`;
- } else {
- message = await this.doAdd(param);
- }
- AppStorage.setOrCreate('lastIntentMsg', message);
- AppStorage.setOrCreate('intentTick', Date.now());
- return { code: 0, result: { message } } as insightIntent.ExecuteResult;
- }
- }
复制代码
五、意图结果的 UI 回流机制
意图执行后数据变更,若应用已在前台,`onPageShow` 不会触发。本 Demo 采用全局状态 + `@Watch` 响应式机制:执行器将结果写入 `AppStorage`,页面用 `@StorageLink` 监听。使用递增的 `intentTick` 时间戳作为触发信号,避免重复监听。关键代码:- @StorageLink('intentTick') @Watch('onIntentResult') intentTick: number = 0;
- @StorageLink('lastIntentMsg') lastIntentMsg: string = '';
- @StorageLink('intentJumpTab') intentJumpTab: number = -1;
- onIntentResult(): void {
- promptAction.showToast({ message: '🎙 小艺:' + this.lastIntentMsg, duration: 2800 });
- if (this.intentJumpTab >= 0 && this.intentJumpTab <= 2) {
- this.current = this.intentJumpTab;
- }
- }
复制代码 这种“时间戳驱动响应式刷新”的方法,是调试中总结的实用技巧。
六、导航能力:真实拉起地图应用
“导航”意图需要真实拉起地图应用。鸿蒙中通过 `startAbility` + Want 实现。由于不同设备预装地图不同,设计了一条降级调用链:依次尝试 Petal Maps、高德、百度,任一成功即返回,全部失败时使用系统隐式 Want 兜底。- export async function openNavigation(context: common.UIAbilityContext, dest: string): Promise<boolean> {
- const schemes = [
- { name: 'PetalMaps', uri: `maps://routes?daddr=${encodeURIComponent(dest)}&type=drive` },
- { name: 'Amap', uri: `amapuri://route/plan/?dname=${encodeURIComponent(dest)}&t=0` },
- { name: 'BaiduMap', uri: `baidumap://map/direction?destination=${encodeURIComponent(dest)}&mode=driving` }
- ];
- for (const s of schemes) {
- try {
- await context.startAbility({ uri: s.uri });
- return true;
- } catch (e) { /* 未安装,继续尝试 */ }
- }
- try {
- await context.startAbility({ action: 'ohos.want.action.viewData', uri: `geo:0,0?q=${encodeURIComponent(dest)}` });
- return true;
- } catch (e) { return false; }
- }
复制代码 这一设计体现了 Agent 化思路:应用将“导航”子任务委托给系统中最适合的提供方,形成能力协作网络。
七、双端适配:手机与鸿蒙 PC 一致运行
本 Demo 未为 PC 单独编写布局代码,即可在手机和鸿蒙 PC 上直接运行。实现原则:弹性布局(`layoutWeight`、百分比、`Flex`)、安全区适配(`getWindowAvoidArea` 获取状态栏高度存入全局)、组件化复用(`TripCard` 独立组件)。更重要的是,意图能力天然跨端——同一份 `insight_intents.json` 在手机和 PC 上均可通过小艺触发,因为意图注册于系统层面,而非某一屏幕。从模拟器到真机手机再到鸿蒙 PC,全程无需修改代码。
八、典型问题与解决方案
8.1 语音指令被分发至系统日历
原因是真实小艺基于华为云端已上架的意图库匹配,本应用的 AddTrip 仅为本地声明,未通过 AppGallery Connect 完成意图框架能力申请与上架。本地声明仅解决“应用内部知晓”,平台上架才使小艺云端感知。本地验证可采用 `aa start` 命令直接投递意图。
8.2 未上架情况下的意图链路验证
使用 hdc shell 命令:- hdc shell "aa start -a EntryAbility -b com.example.smarttrip --ps op add --ps destination '首都机场' --ps type 'flight' --ps when 'tomorrow 9'"
复制代码 注意使用 `--ps` 直接写入 `want.parameters`,避免 URI 中 `&` 被 shell 解析为分隔符。
8.3 命令行中文参数乱码
hdc shell 处理中文参数时编码错乱,但在真实场景中小艺采用结构化参数传递不受影响。应对方案:添加英文 key 到中文的映射,本地测试使用英文参数。
8.4 环境变量污染导致构建失败
报错 `ERR_WORKER_INVALID_EXEC_ARGV: --report-on-fatalerror is not allowed in NODE_OPTIONS`,因 `NODE_OPTIONS` 被外部工具污染。解决方案:彻底退出 DevEco Studio,清除该环境变量后重新启动。
九、意图框架的工程价值
完成本 Demo 后,可以明确感受到意图即服务的价值:
- 降低流量获取压力,竞争维度转为能力可靠程度;
- 垂直应用可通过意图分发被精准调用,对独立开发者友好;
- 促使开发者重新审视应用边界,将子任务委托给专业能力方。
同时,开发者需要掌握面向 AI 的能力说明撰写(`llmDescription`)、返回值语音播报优化、跨端弹性设计等新课题。
十、结语
在 Agent 时代,一个应用真正的起点不是它的首个页面,而是它向系统声明的能力契约。「小艺出行」Demo 虽只有三个页面和三个意图,却完整呈现了从“开发一个供用户主动打开的应用”到“构建一个供系统随时调用的 Agent 能力”的全过程,包括技术难点与解决方案。对于正在开发鸿蒙应用的开发者,建议选取熟悉场景,编写意图声明并接入小艺,真切体验这一范式转变的工程落地。 |