校园嗨购 · 某购物节特别版:HarmonyOS 7「小艺意图框架 + 碰一碰拼单分享」外延入口实战
校园嗨购 · 某购物节特别版:HarmonyOS 7「小艺意图框架 + 碰一碰拼单分享」外延入口实战

官方文档对齐基准(2026-10-26 逐条核对 developer.huawei.com,字段名 / 枚举值 / 配置文件结构全部来自官网当前版本)
能力 模块 / 文件(真实路径) 官方文档 更新时间 意图执行基类(意图调用) import { InsightIntentExecutor, insightIntent } from '@kit.AbilityKit';developer.huawei.com/consumer/cn/doc/doccenter-references/api/js-apis-app-ability-insightintentexecutor2026-09-23 意图基础定义 / 枚举 / 结果类型 import { insightIntent } from '@kit.AbilityKit';developer.huawei.com/consumer/cn/doc/doccenter-references/api/js-apis-app-ability-insightintent2026-09-23 意图共享(上报小艺建议) import { insightIntent } from '@kit.IntentsKit';developer.huawei.com/consumer/cn/doc/doccenter-references/api/intents-arkts-api-insightintent2026-08-29 意图配置文件 src/main/resources/base/profile/insight_intent.jsondeveloper.huawei.com/consumer/cn/doc/doccenter-capabilities/insight-intent-config-development2026-09-09 装饰器方式(API 20+ 推荐) @InsightIntentEntry/InsightIntentEntryExecutordeveloper.huawei.com/consumer/cn/doc/doccenter-capabilities/insight-intent-decorator-development2026-09-09 系统分享 import { systemShare } from '@kit.ShareKit';developer.huawei.com/consumer/cn/doc/doccenter-references/api/share-system-share2026-09-09 碰一碰 / 隔空传送分享 import { systemShare, harmonyShare } from '@kit.ShareKit';developer.huawei.com/consumer/cn/doc/doccenter-capabilities/knock-share-between-phones-content2026-09-09 产品版本:校园嗨购 v2.8.0 · 某购物节特别版(2026-11)
技术基线:HarmonyOS 7 / API 26 · Stage 模型 · ArkTS
核心关键字:insight_intent.json·InsightIntentExecutor·onExecuteInUIAbilityForegroundMode·insightIntent.ExecuteMode·shareIntent·systemShare.SharedData·ShareController.show·harmonyShare.on('knockShare')
本文配图:mockups/16_intents_share_cover.html(封面)·mockups/17_intents_share_arch.html(全链路架构图)
一、购物节的竞争,最后落在「入口」上
购物节期间用户的注意力被切得極碎:缓存区里躺着十几个购物 App,谁离用户的手最近,谁就拿走这一单。而「离手最近」在 HarmonyOS 上有两个非常具体的位置:
- 小艺的对话与建议——用户说一句「我的快递到哪了」,系统把请求直接下发到你的 App;
- 分享面板与碰一碰——拼单这件事天然需要两个人,分享链路每多一步,成团率就掉一截。
| 用户痛点 | 传统做法 | 数据表现 |
|---|---|---|
| ① 找入口太深:查快递要「打开 App → 我的 → 订单 → 某个订单 → 物流详情」5 步 | 桌面卡片只能解决一半(卡片也有位置成本) | 查快递平均点击 5.2 步,其中 34% 用户中途放弃 |
| ② 拼单分享链路长:复制链接 → 切微信 → 粘链接 → 发群 → 等人点 | 三方 App 跳转,链路完全不可控 | 邀请链接点击率 11.3%,成团率被分享摩擦吃掉一半 |
| ③ 老客复购要重新找商品:每次都要重搜「上次那箱零食」 | 只能靠收藏夹,很多用户根本不收藏 | 老客复购平均耗时 2 分 40 秒 |
| 指标 | 接入前 | 接入后 | 变化 |
|---|---|---|---|
| 查快递平均操作步数 | 5.2 步 | 1 句语音 | 入口从"层级"变成"语言" |
| 拼单邀请链接点击率 | 11.3% | 42.7% | ↑ 31.4pp(系统分享面板 + 碰一碰) |
| 碰一碰拼单成团率 | — | 68.5% | 两台手机碰一下直接进同一拼单团 |
| 老客复购耗时 | 2 分 40 秒 | 8 秒 | 意图调用直接后台加购 |
这一篇会有两个「反常识」的结论,先放在这里:
- 意图框架不是「无门槛能力」:它需要 AGC 能力申请 + 小艺开放平台注册,仅中国境内、仅企业开发者、且不支持模拟器。
- 官方没有
ShareData这个类:真实体系是SharedData+SharedRecord,而且show()的第二个参数不可省略。
二、官方 API 完整速查表
2.1 意图框架(Intents Kit)总览:两条正交链路
官方中文名是 「意图框架」,它把应用能力以「意图」的形式开放给系统入口(小艺建议 / 小艺对话 / 小艺搜索)。开发者要理解的是两条方向相反的链路:
| 链路 | 方向 | 官方入口 | 本项目的用法 |
|---|---|---|---|
| 意图调用(执行) | 系统 → 应用 | 小艺对话(技能调用)、小艺搜索 | 用户说「我的快递到哪了」→ 拉起实况窗页面 |
| 意图共享(数据上报) | 应用 → 系统 | 小艺建议(习惯 / 事件 / 位置推荐) | 把「常在 20:00 买宿舍零食」上报,让小艺在合适时机推荐 |
四条硬门槛(写代码之前必须确认,否则全部白做):
| 维度 | 官方结论 |
|---|---|
| 触发入口 | 小艺建议、小艺对话、小艺搜索 |
| 支持设备 | Phone、Tablet、PC / 2in1 |
| 支持地区 | 仅中国境内(香港、澳门、中国台湾除外) |
| 系统版本 | HarmonyOS 5.0 及以上 |
| 开发主体 | 仅面向企业开发者,个人开发者无法申请 |
| 模拟器 | 本 Kit 暂不支持模拟器(必须真机) |
| 必做申请 | AGC 能力申请:项目设置 > 开放能力管理 > 意图框架(约 1~3 个工作日) |
| 上线注册 | 上架需在应用市场 + 小艺开放平台做意图注册(约 3~5 个工作日) |
2.2 意图定义:配置文件方式 vs 装饰器方式
官方提供两代方案,二选一:
| 方式 | 起始版本 | 官方态度 | 载体 |
|---|---|---|---|
| 配置文件方式 | API 11 | 兼容保留 | insight_intent.json |
| 装饰器方式 | API 20 | 官方推荐 | 代码里的装饰器 + insight_intent.json 声明 insightIntentsSrcEntry |
① 配置文件:insight_intent.json(单数!)
- 路径:
module 目录 > src > main > resources > base > profile/insight_intent.json - 约束:整个工程只能有一个该文件
{
"insightIntents": [
{
"intentName": "QueryOrderLogistics",
"domain": "ShoppingDomain",
"intentVersion": "1.0.1",
"srcEntry": "./ets/insightintents/QueryOrderLogisticsExecutor.ets",
"displayName": "查快递",
"displayDescription": "查询校园嗨购订单的物流进度",
"keywords": ["查快递", "我的快递", "物流到哪了"],
"uiAbility": {
"ability": "EntryAbility",
"executeMode": ["foreground", "background"]
}
}
]
}
字段真值表:
| 字段 | 说明 |
|---|---|
insightIntents | 顶层数组(不是 insightIntentEntities) |
intentName | 意图名称,意图唯一标识 |
domain | 意图垂域名称,如 ShoppingDomain |
intentVersion | 意图版本,当前为 "1.0.1" |
srcEntry | 执行文件相对路径,长度 ≤ 127 字节 |
uiAbility | 绑定 UIAbility,内含 ability + executeMode |
executeMode | JSON 中为字符串数组,取值仅 "foreground" / "background" |
uiExtension / serviceExtension / form | 分别绑定 UIExtensionAbility / ServiceExtensionAbility(form 内含 ability + formName) |
displayName / displayDescription / icon | 展示信息 |
keywords | 字符串数组,供小艺匹配 |
inputParams / outputParams | JSON-Schema 风格入参 / 出参声明 |
entities | 实体声明(不是 entitiesName) |
insightIntentsSrcEntry | 数组,元素形如 { "srcEntry": "./ets/..." };用于声明未被其它文件 import 的执行文件参与编译 |
⚠️ 常见误写:
insight_intents.json(复数)、executionMode(多了 ion)、insightIntentEntities—— 官方都不是这么写的。
⚠️ 官方三篇指南(配置文件开发 / 装饰器开发 / 习惯推荐接入)均未提及需要在module.json5里做 metadata 声明。网上流传的ohos.ability.intent.metadata+$profile:insight_intents写法没有官方出处,不要照抄。
② 装饰器方式(API 20+,官方推荐)
真实存在的装饰器清单:
@InsightIntentEntry // 入口意图(最常用)
@InsightIntentLink // 链接意图
@InsightIntentPage // 页面意图
@InsightIntentFunctionMethod // 函数意图
@InsightIntentForm // 卡片意图
@InsightIntentEntity // 实体
2.3 意图执行器(意图调用侧,在 @kit.AbilityKit,不在 IntentsKit)
import { InsightIntentExecutor, insightIntent } from '@kit.AbilityKit';
| 回调(官方真实签名) | 触发场景 |
|---|---|
onExecuteInUIAbilityForegroundMode(intentName: string, params: Record<string, Object>, windowStage: window.WindowStage): Promise<insightIntent.ExecuteResult> | 意图以前台方式拉起 UIAbility |
onExecuteInUIAbilityBackgroundMode(intentName: string, params: Record<string, Object>): Promise<insightIntent.ExecuteResult> | 意图后台执行,不拉起界面 |
onExecuteInUIExtensionAbility(name: string, param: Record<string, Object>, pageLoader: UIExtensionContentSession) | 绑定 UIExtensionAbility |
onExecuteInServiceExtensionAbility(name: string, param: Record<string, Object>) | 绑定 ServiceExtensionAbility |
- 类属性:
context: InsightIntentContext - 系统能力:
SystemCapability.Ability.AbilityRuntime.Core - 基类名是
InsightIntentExecutor,不是IntentExecutor
// insightIntent.ExecuteMode 枚举真值
insightIntent.ExecuteMode.UI_ABILITY_FOREGROUND = 0;
insightIntent.ExecuteMode.UI_ABILITY_BACKGROUND = 1;
insightIntent.ExecuteMode.UI_EXTENSION_ABILITY = 2;
// 执行结果
interface ExecuteResult {
code: number; // 业务返回码
result?: Record<string, Object>; // 业务数据
uris?: Array<string>; // 18+
flags?: number; // 18+
}
2.4 意图共享(@kit.IntentsKit)
import { insightIntent } from '@kit.IntentsKit';
import { common } from '@kit.AbilityKit';
function shareIntent(context: common.BaseContext, intents: Array<InsightIntent>): Promise<void>;
function shareIntent(context: common.BaseContext, intents: Array<InsightIntent>,
callback: AsyncCallback<void>): void;
function deleteIntent(context: common.BaseContext, intentName: string, identifiers?: Array<string>): Promise<void>;
interface InsightIntent {
intentName: string;
intentVersion: string; // 当前 "1.0.1"
identifier: string; // UUID,删除时按它定位
intentActionInfo: IntentActionInfo;
intentEntityInfo: IntentEntityInfo;
}
配额(官方明确,务必做本地限流):
| 限制项 | 数值 |
|---|---|
| 单应用每天共享次数 | ≤ 20 次 |
| 单次数据体积 | ≤ 50KB |
| 所有接入方每天总上限 | 3000 次 |
- 系统能力:
SystemCapability.AI.InsightIntent,起始4.0.0(10) - 错误码:
401、1000101101、1000101102、1000101103、1000101104、1000101105、1000101106、1000101201(具体含义以官方错误码页为准,本文不做推测)
2.5 Share Kit:系统分享 / 碰一碰 / 隔空传送
import { systemShare } from '@kit.ShareKit'; // 系统分享
import { systemShare, harmonyShare } from '@kit.ShareKit'; // 含碰一碰 / 隔空传送
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
- 起始版本
4.1.0(11);系统能力SystemCapability.Collaboration.SystemShare - Share Kit 支持模拟器(与意图框架相反)
- 不存在
ShareData/ShareDataContent/uniformDataStruct,真实体系是SharedData+SharedRecord
// 数据侧
class SharedData {
constructor(record: SharedRecord);
addRecord(record: SharedRecord): void;
getRecords(): Array<SharedRecord>;
}
interface SharedRecord {
utd: string; // 必填
title?: string;
label?: string;
description?: string;
thumbnail?: Uint8Array;
thumbnailUri?: string; // 12+
uri?: string; // 与 content 至少填一个
content?: string;
extraData?: Record<string, string | number | boolean | Array<...>>;
revisitShareRecordData?; // 23+
}
// 控制侧
class ShareController {
constructor(data: SharedData);
show(context: common.UIAbilityContext, options: ShareControllerOptions): Promise<void>;
on(event: 'dismiss', callback: () => void): void;
off(event: 'dismiss', callback: () => void): void;
on(event: 'shareCompleted', callback: (result: ShareOperationResult) => void): void; // 5.1.0(18)+
}
interface ShareControllerOptions {
anchor?: ShareControllerAnchor | string; // 锚点,可传组件 id 字符串
previewMode?: SharePreviewMode;
selectionMode?: SelectionMode;
excludedAbilities?: Array<string>; // 12+
appLaunchTrustInfo?; // 21+,需权限 ohos.permission.SET_SYSTEMSHARE_APPLAUNCHTRUSTLIST
}
枚举真值:
systemShare.SharePreviewMode.DEFAULT = 0; // 默认预览
systemShare.SharePreviewMode.DETAIL = 1; // 详情预览
systemShare.SelectionMode.SINGLE = 0; // 单选
systemShare.SelectionMode.BATCH = 1; // 多选
systemShare.ShareAbilityResultCode.ERROR = -1;
systemShare.ShareAbilityResultCode.BACK = 0;
systemShare.ShareAbilityResultCode.CLOSE = 1;
分享结果(18+):
interface ShareOperationResult { targetAbilityInfo: ShareAbilityInfo; }
interface ShareAbilityInfo { name: string; }
| 渠道 | ShareAbilityName 真值 |
|---|---|
| 复制到剪贴板 | SystemShare_CopyToPasteboard |
| 保存到图库 | SystemShare_SaveToMediaAsset |
| 另存为文件 | SystemShare_SaveAsFile |
| 打印 | SystemShare_Print |
| 保存到中转站 | SystemShare_Superhub |
| 收藏 | SystemShare_Collection |
| 华为分享 | SystemShare_HarmonyShare |
| 加密分享 | SystemShare_Encrypt |
渠道名规则:系统渠道返回上表固定名;三方渠道为
bundleName#moduleName#abilityName。这就是做「分享漏斗」埋点的数据基础。
碰一碰 / 隔空传送:
// 碰一碰分享
let registry: harmonyShare.SendCapabilityRegistry = { windowId: /* 当前窗口 id */ };
harmonyShare.on('knockShare', registry,
(sharableTarget: harmonyShare.SharableTarget) => {
sharableTarget.share(sharedData); // 把数据交给碰一碰卡片
});
harmonyShare.off('knockShare', registry);
// 隔空传送
harmonyShare.on('gesturesShare', (sharableTarget: harmonyShare.SharableTarget) => { /* ... */ });
harmonyShare.off('gesturesShare', callback);
| 要点 | 官方说明 |
|---|---|
| 能力探测 | canIUse('SystemCapability.Collaboration.HarmonyShare') |
| 环境要求 | 双端亮屏 + 解锁 + 已开启华为分享 |
| 隔空传送时序 | 回调中建议 3 秒内调用 sharableTarget.share(),否则可能超时 |
| 生命周期 | 离开可分享页面(含应用退后台)必须取消监听 |
| 结果获取 | 宿主应用无法获得碰一碰分享结果,由系统通知告知用户 |
| 附加能力 | updateShareData({ thumbnailUri }) / reject(harmonyShare.SharableErrorCode.DOWNLOAD_ERROR)(5.0.3/15+) / clarifyNonShare({ message })(6.0.2/22+) |
错误码:401 参数错误 · 1003702001 记录类型不支持 · 1003702002 IPC 数据过大。
接收端(被分享方):
function getSharedData(want: Want): Promise<SharedData>;
function getContactInfo(want: Want): Promise<ContactInfo>;
// module.json5:接收方需声明 skills
"skills": [
{
"actions": ["ohos.want.action.sendData"],
"uris": [
{ "scheme": "file", "utd": "general.text", "maxFileSupported": 1 },
{ "scheme": "file", "utd": "general.png", "maxFileSupported": 1 }
]
}
]
三、完整实现:从「一句话」到「碰一下」
3.1 意图注册:insight_intent.json
{
"insightIntents": [
{
"intentName": "QueryOrderLogistics",
"domain": "ShoppingDomain",
"intentVersion": "1.0.1",
"srcEntry": "./ets/insightintents/QueryLogisticsExecutor.ets",
"displayName": "查快递",
"displayDescription": "查询校园嗨购订单的物流进度并点亮实况窗",
"keywords": ["查快递", "我的快递", "物流到哪了", "还有多久到"],
"uiAbility": {
"ability": "EntryAbility",
"executeMode": ["foreground"]
},
"inputParams": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "订单号,可为空(默认查最近一单)" }
}
}
},
{
"intentName": "RepeatLastOrder",
"domain": "ShoppingDomain",
"intentVersion": "1.0.1",
"srcEntry": "./ets/insightintents/RepeatLastOrderExecutor.ets",
"displayName": "再买一次",
"displayDescription": "把上一单商品直接加入购物车",
"keywords": ["再买一次", "上次那个再来一份", "复购"],
"uiAbility": {
"ability": "EntryAbility",
"executeMode": ["background"]
}
}
]
}
注意两点:
executeMode在 JSON 里是字符串数组("foreground"/"background"),而枚举insightIntent.ExecuteMode.UI_ABILITY_FOREGROUND只在装饰器 / ArkTS 代码里用,两者不要混。
3.2 QueryLogisticsExecutor.ets —— 前台意图:一句话查快递
/**
* @file QueryLogisticsExecutor.ets
* @brief 意图执行器(前台):小艺说"我的快递到哪了"→ 拉起实况窗页面
* API 对齐 @kit.AbilityKit · InsightIntentExecutor(HarmonyOS 7 / API 26)
*/
import { InsightIntentExecutor, insightIntent } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'QueryLogisticsExecutor';
export default class QueryLogisticsExecutor extends InsightIntentExecutor {
/**
* 前台执行:官方签名 (intentName, params, windowStage)
* windowStage 可用于 loadContent 指定要展示的页面
*/
async onExecuteInUIAbilityForegroundMode(
intentName: string,
params: Record<string, Object>,
windowStage: window.WindowStage
): Promise<insightIntent.ExecuteResult> {
hilog.info(DOMAIN, TAG, `foreground intent=${intentName}, params=${JSON.stringify(params)}`);
// 意图参数:官方约定从 params 里按 inputParams 声明的键取值
let orderId: string = '';
const rawOrderId: Object | undefined = params['orderId'];
if (typeof rawOrderId === 'string') {
orderId = rawOrderId;
}
try {
// 把意图参数转交页面:页面自己去取物流 + 点亮实况窗
AppStorage.setOrCreate<string>('intentQueryOrderId', orderId);
AppStorage.setOrCreate<string>('intentSource', 'xiaoyi_foreground');
// 官方要求:前台意图要真正把页面铺出来
const storage: LocalStorage = new LocalStorage({ orderId: orderId });
windowStage.loadContent('pages/LogisticsLiveViewPage', storage);
const result: insightIntent.ExecuteResult = {
code: 0,
result: { handled: true, page: 'LogisticsLiveViewPage' }
};
return result;
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `loadContent failed. code=${err.code}`);
const fail: insightIntent.ExecuteResult = {
code: -1,
result: { handled: false, reason: 'load_content_failed' }
};
return fail;
}
}
/**
* 同一意图如果被声明了 background 模式,也要实现,否则后台调用会拿不到结果
*/
async onExecuteInUIAbilityBackgroundMode(
intentName: string,
params: Record<string, Object>
): Promise<insightIntent.ExecuteResult> {
hilog.info(DOMAIN, TAG, `background intent=${intentName}`);
const result: insightIntent.ExecuteResult = {
code: 0,
result: { handled: true, mode: 'background' }
};
return result;
}
}
3.3 装饰器方式(API 20+,官方推荐写法)
同样是「再买一次」,用装饰器可以省掉手写 JSON 的一半字段:
/**
* @file RepeatLastOrderIntent.ets
* @brief 装饰器方式声明意图(API 20+ 官方推荐)
*/
import { InsightIntentEntry, InsightIntentEntryExecutor, insightIntent } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
/** 结果定义 */
interface RepeatOrderResultDef {
cartCount: number;
amount: string;
}
@InsightIntentEntry({
intentName: 'RepeatLastOrder',
domain: 'ShoppingDomain',
intentVersion: '1.0.1',
displayName: '再买一次',
displayDescription: '把上一单商品直接加入购物车',
icon: $r('app.media.ic_repeat_order'),
llmDescription: '支持传入商品名称或订单号,把对应商品重新加入购物车',
keywords: ['再买一次', '上次那个再来一份', '复购'],
abilityName: 'EntryAbility',
// ⚠️ 装饰器里用枚举,JSON 里用字符串,别混
executeMode: [insightIntent.ExecuteMode.UI_ABILITY_BACKGROUND],
parameters: {
type: 'object',
properties: {
keyword: { type: 'string', description: '商品关键词,如"宿舍零食大礼包"' }
},
required: ['keyword']
}
})
export default class RepeatLastOrderIntent
extends InsightIntentEntryExecutor<RepeatOrderResultDef> {
/** 装饰器方式下,意图参数通过同名公开属性注入 */
public keyword: string = '';
async onExecute(): Promise<insightIntent.IntentResult<RepeatOrderResultDef>> {
hilog.info(DOMAIN, 0x0000, `repeat order keyword=${this.keyword}`);
// 真实业务:查本地缓存的上一次订单 → 调服务端加购接口
const cartCount: number = await this.addToCart(this.keyword);
const result: insightIntent.IntentResult<RepeatOrderResultDef> = {
code: 0,
result: { cartCount: cartCount, amount: '39.90' }
};
return result;
}
private async addToCart(keyword: string): Promise<number> {
// 省略:调用服务端 /cart/add,返回购物车商品数
return keyword.length > 0 ? 3 : 0;
}
}
用了装饰器后,
insight_intent.json中需要补insightIntentsSrcEntry声明执行文件参与编译:{ "insightIntentsSrcEntry": [{ "srcEntry": "./ets/insightintents/RepeatLastOrderIntent.ets" }] }
3.4 InsightShareReport.ets —— 把小票数据共享给小艺建议
/**
* @file InsightShareReport.ets
* @brief 意图共享:把"常在 20:00 买宿舍零食"上报给小艺建议
* API 对齐 @kit.IntentsKit · insightIntent.shareIntent
* 配额:单应用 20 次/天、单次 50KB —— 必须本地限流
*/
import { insightIntent } from '@kit.IntentsKit';
import { common } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'InsightShareReport';
const STORE_NAME: string = 'insight_quota';
export class InsightShareReport {
/** 一天只允许上报 20 次:本地做硬闸门,超了直接丢弃,避免触发 1000101xxx */
private static readonly DAILY_LIMIT: number = 20;
static async reportPurchaseHabit(context: common.BaseContext, goodsName: string,
buyHour: number): Promise<boolean> {
if (!await InsightShareReport.consumeQuota(context)) {
hilog.warn(DOMAIN, TAG, 'daily quota exhausted, drop report');
return false;
}
const entity: insightIntent.InsightIntent = {
intentName: 'PurchaseHabit',
intentVersion: '1.0.1',
identifier: InsightShareReport.uuid(),
intentActionInfo: { actionType: 'BUY', actionName: goodsName },
intentEntityInfo: { entityName: goodsName, buyHour: buyHour }
};
const intents: Array<insightIntent.InsightIntent> = [entity];
try {
await insightIntent.shareIntent(context, intents);
hilog.info(DOMAIN, TAG, `shareIntent ok: ${goodsName} @${buyHour}`);
return true;
} catch (error) {
const err = error as BusinessError;
// 401 参数错误 / 1000101101 ~ 1000101106 业务异常 / 1000101201
hilog.error(DOMAIN, TAG, `shareIntent failed. code=${err.code}, msg=${err.message}`);
return false;
}
}
/** 用户撤回授权 / 数据删除时,按 identifier 精确删除 */
static async revoke(context: common.BaseContext, intentName: string,
identifiers: Array<string>): Promise<void> {
try {
await insightIntent.deleteIntent(context, intentName, identifiers);
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `deleteIntent failed. code=${err.code}`);
}
}
/** 本地配额:按天计数 */
private static async consumeQuota(context: common.BaseContext): Promise<boolean> {
const store: preferences.Preferences = await preferences.getPreferences(context, STORE_NAME);
const today: string = new Date().toDateString();
const savedDay: string = await store.get('day', '') as string;
let count: number = await store.get('count', 0) as number;
if (savedDay !== today) {
count = 0;
}
if (count >= InsightShareReport.DAILY_LIMIT) {
return false;
}
await store.put('day', today);
await store.put('count', count + 1);
await store.flush();
return true;
}
private static uuid(): string {
return `${Date.now()}-${Math.floor(Math.random() * 100000)}`;
}
}
3.5 GroupBuyShareService.ets —— 系统分享面板(拼单)
/**
* @file GroupBuyShareService.ets
* @brief 拼单分享:SharedData + SharedRecord + ShareController
* API 对齐 @kit.ShareKit · systemShare(起始 4.1.0(11))
*/
import { common } from '@kit.AbilityKit';
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'GroupBuyShare';
/** 拼单团数据 */
export interface GroupBuyTeam {
teamId: string;
goodsName: string;
originPrice: string;
teamPrice: string;
link: string;
}
export class GroupBuyShareService {
private controller: systemShare.ShareController | undefined = undefined;
/**
* 拉起系统分享面板
* @param context 官方签名要求 common.UIAbilityContext
* @param anchor 分享按钮的组件 id(面板从按钮位置弹出)
*/
async shareTeam(context: common.UIAbilityContext, anchor: string,
team: GroupBuyTeam): Promise<void> {
// ① 文本记录:utd 必填,content / uri 至少填一个
const textRecord: systemShare.SharedRecord = {
utd: utd.UniformDataType.PLAIN_TEXT,
title: `【拼单】${team.goodsName}`,
description: `原价 ${team.originPrice},拼单价 ${team.teamPrice},差 1 人成团`,
content: `${team.goodsName} 拼单价 ${team.teamPrice},点我一起拼 → ${team.link}`,
extraData: { teamId: team.teamId, channel: 'systemShare' }
};
// ② 链接记录:用 HYPERLINK 让系统识别为可点击链接
const linkRecord: systemShare.SharedRecord = {
utd: utd.UniformDataType.HYPERLINK,
title: '校园嗨购 · 拼单直达',
uri: team.link
};
// ③ 组装 SharedData(注意:不是 ShareData)
const sharedData: systemShare.SharedData = new systemShare.SharedData(textRecord);
sharedData.addRecord(linkRecord);
this.controller = new systemShare.ShareController(sharedData);
// ④ 面板关闭
this.controller.on('dismiss', () => {
hilog.info(DOMAIN, TAG, 'share panel dismissed');
});
// ⑤ 分享结果(5.1.0(18)+):渠道名可用于埋点
this.controller.on('shareCompleted', (result: systemShare.ShareOperationResult) => {
const channel: string = result.targetAbilityInfo.name;
hilog.info(DOMAIN, TAG, `shareCompleted channel=${channel}`);
// 系统渠道是固定名,三方渠道是 bundleName#moduleName#abilityName
AppStorage.setOrCreate<string>('lastShareChannel', channel);
});
const options: systemShare.ShareControllerOptions = {
anchor: anchor,
previewMode: systemShare.SharePreviewMode.DEFAULT,
selectionMode: systemShare.SelectionMode.SINGLE
};
try {
// ⚠️ options 不可省略,官方示例都传了对象
await this.controller.show(context, options);
} catch (error) {
const err = error as BusinessError;
// 401 参数错误 / 1003702001 记录类型不支持 / 1003702002 IPC 数据过大
hilog.error(DOMAIN, TAG, `show failed. code=${err.code}, msg=${err.message}`);
}
}
/** 页面销毁时务必摘掉监听 */
dispose(): void {
if (this.controller !== undefined) {
this.controller.off('dismiss', () => {});
}
}
}
3.6 KnockShareService.ets —— 碰一碰 / 隔空传送分享
/**
* @file KnockShareService.ets
* @brief 碰一碰拼单 + 隔空传送
* API 对齐 @kit.ShareKit · harmonyShare
*/
import { harmonyShare, systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'KnockShare';
export class KnockShareService {
private registry: harmonyShare.SendCapabilityRegistry | undefined = undefined;
private shareData: systemShare.SharedData | undefined = undefined;
/** 进入拼单页时注册;官方要求:进页面注册、离开必须取消 */
register(windowId: number, goodsName: string, teamPrice: string, link: string): boolean {
if (!canIUse('SystemCapability.Collaboration.HarmonyShare')) {
hilog.warn(DOMAIN, TAG, 'HarmonyShare not supported on this device');
return false;
}
const record: systemShare.SharedRecord = {
utd: utd.UniformDataType.HYPERLINK,
title: `${goodsName} · 拼单价 ${teamPrice}`,
description: '碰一碰加入我的拼单团',
uri: link
};
this.shareData = new systemShare.SharedData(record);
this.registry = { windowId: windowId };
// ① 碰一碰分享:两台手机碰一下,触发对端卡片
harmonyShare.on('knockShare', this.registry,
(sharableTarget: harmonyShare.SharableTarget) => {
hilog.info(DOMAIN, TAG, 'knockShare triggered');
if (this.shareData !== undefined) {
sharableTarget.share(this.shareData);
}
});
// ② 隔空传送:官方建议 3 秒内调用 share(),否则可能超时
harmonyShare.on('gesturesShare', (sharableTarget: harmonyShare.SharableTarget) => {
hilog.info(DOMAIN, TAG, 'gesturesShare triggered');
if (this.shareData !== undefined) {
sharableTarget.share(this.shareData);
}
});
return true;
}
/** 页面 aboutToDisappear / 应用退后台时调用 */
unregister(): void {
if (this.registry !== undefined) {
harmonyShare.off('knockShare', this.registry);
this.registry = undefined;
}
harmonyShare.off('gesturesShare', () => {});
this.shareData = undefined;
hilog.info(DOMAIN, TAG, 'knock/gestures share unregistered');
}
}
3.7 接收端:别人分享过来,我怎么接
/**
* @file ShareReceiveAbility.ets
* @brief 被分享方:从 want 里把 SharedData 取出来
* 接收方需在 module.json5 声明 skills.actions = ["ohos.want.action.sendData"]
*/
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { systemShare } from '@kit.ShareKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'ShareReceive';
export default class ShareReceiveAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleSharedData(want);
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleSharedData(want);
}
private async handleSharedData(want: Want): Promise<void> {
try {
const sharedData: systemShare.SharedData = await systemShare.getSharedData(want);
const records: Array<systemShare.SharedRecord> = sharedData.getRecords();
records.forEach((record: systemShare.SharedRecord) => {
hilog.info(DOMAIN, TAG, `utd=${record.utd}, title=${record.title}, uri=${record.uri}`);
if (record.extraData !== undefined && record.extraData['teamId'] !== undefined) {
// 直接跳进同一个拼单团
AppStorage.setOrCreate<string>('joinTeamId', record.extraData['teamId'] as string);
}
});
} catch (error) {
hilog.error(DOMAIN, TAG, `getSharedData failed`);
}
}
}
四、易错点对照表(照着改,能省一天)
| # | 常见错误写法 | 官方正确写法 |
|---|---|---|
| 1 | insight_intents.json(复数) | insight_intent.json(单数,且整个工程只能有一个) |
| 2 | IntentExecutor | InsightIntentExecutor |
| 3 | executionMode | executeMode |
| 4 | JSON 里写 executeMode: [ExecuteMode.UI_ABILITY_FOREGROUND] | JSON 里必须是字符串 ["foreground"];枚举只在装饰器 / ArkTS 里用 |
| 5 | insightIntentEntities / entitiesName | entities |
| 6 | 在 module.json5 配 ohos.ability.intent.metadata | 官方未要求任何 metadata 声明(该写法无官方出处) |
| 7 | ShareData(类) | SharedData + SharedRecord |
| 8 | ShareDataContent(分 default / utd 两种写法) | 不存在;统一用 SharedRecord { utd, content / uri } |
| 9 | shareIntent 当查询接口用,或找 getAllInsightIntent | 不存在;shareIntent 上报、deleteIntent / deleteEntity 删除 |
| 10 | ShareController.show(context) 省略第二参数 | ShareControllerOptions 不可省略 |
| 11 | show() 第一参数传 common.Context | 官方是 common.UIAbilityContext |
| 12 | 在 @kit.IntentsKit 里找执行器基类 | 执行器在 @kit.AbilityKit;@kit.IntentsKit 只提供意图共享 |
| 13 | 小看意图框架的门槛 | 需 AGC 能力申请 + 小艺开放平台注册,仅中国境内、仅企业开发者、不支持模拟器 |
| 14 | 碰一碰分享后想拿结果 | 官方明确:宿主应用无法获得碰一碰分享结果,系统会通知用户 |
五、错误码处置表
| 模块 | 错误码 | 官方含义 | 线上处置 |
|---|---|---|---|
| 意图共享 | 401 | 参数错误 | 校验 InsightIntent 五个必填字段是否齐全 |
| 意图共享 | 1000101101 ~ 1000101106 | 业务异常族 | 本地限流(≤20 次/天)+ 失败直接丢弃,不重试 |
| 意图共享 | 1000101201 | 业务异常 | 检查 payload 是否超过 50KB |
| 系统分享 | 1003702001 | 记录类型不支持 | SharedRecord.utd 必须是 UniformDataType 里的合法常量 |
| 系统分享 | 1003702002 | IPC 数据过大 | 图片走 uri 而非 content,压缩 thumbnail |
| 扫码直达(前置) | — | App Linking 未生效 | 检查是否手动签名(自动签名不支持 App Linking)+ 域名是否 HTTPS |
意图框架上线清单(缺一项都上不了线):
- AGC 后台申请「意图框架」开放能力(1~3 个工作日);
- 真机验证(模拟器不支持本 Kit);
- 应用市场上架 + 小艺开放平台意图注册(3~5 个工作日);
- 用
keywords覆盖真实用户说法(「我的快递到哪了」「还有多久到」「昨天买的那箱零食」都要写进去)。
六、写在最后
把这一篇的两个能力放在一起看,会发现它们解决的是同一个问题:把「打开 App」这个动作彻底消灭掉。
- 意图框架让入口从「点 5 步」变成「说一句」;
- 系统分享 / 碰一碰让分享从「复制粘链接」变成「碰一下」。
但这两个能力也都有一个共同特征:它们是系统级能力,所以你必须在系统的规则里做事——意图框架有 AGC 与地区限制、有每天 20 次的共享配额;碰一碰有亮屏解锁的前提、且宿主拿不到结果。把这些「限制」写进设计文档,比多写 300 行代码更重要。
购物节真正的胜负手,从来不是商品详情页做得多漂亮,而是 用户想买的那一刻,你的入口在哪里。
附:本文 API 速查卡(可直接贴进代码仓库 README)
// ① 意图执行器(@kit.AbilityKit,注意不在 IntentsKit)
import { InsightIntentExecutor, insightIntent } from '@kit.AbilityKit';
class MyExecutor extends InsightIntentExecutor {
async onExecuteInUIAbilityForegroundMode(
intentName: string, params: Record<string, Object>, windowStage: window.WindowStage
): Promise<insightIntent.ExecuteResult> { /* loadContent + return { code: 0, result: {...} } */ }
async onExecuteInUIAbilityBackgroundMode(
intentName: string, params: Record<string, Object>
): Promise<insightIntent.ExecuteResult> { /* 静默执行 */ }
}
insightIntent.ExecuteMode.UI_ABILITY_FOREGROUND = 0;
insightIntent.ExecuteMode.UI_ABILITY_BACKGROUND = 1;
insightIntent.ExecuteMode.UI_EXTENSION_ABILITY = 2;
// ExecuteResult { code, result?, uris?(18+), flags?(18+) }
// ② 意图共享(@kit.IntentsKit)—— 配额:20 次/天、单次 ≤50KB
import { insightIntent } from '@kit.IntentsKit';
await insightIntent.shareIntent(context /* common.BaseContext */, intents);
await insightIntent.deleteIntent(context, intentName, identifiers);
// InsightIntent { intentName, intentVersion: '1.0.1', identifier, intentActionInfo, intentEntityInfo }
// ③ 意图配置文件:src/main/resources/base/profile/insight_intent.json(单数,全工程唯一)
// { "insightIntents": [ { intentName, domain, intentVersion, srcEntry(≤127B),
// uiAbility: { ability, executeMode: ["foreground","background"] } } ] }
// ④ 系统分享(@kit.ShareKit,起始 4.1.0(11))
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
const data = new systemShare.SharedData({ utd: utd.UniformDataType.PLAIN_TEXT, content: '...' });
data.addRecord({ utd: utd.UniformDataType.HYPERLINK, uri: 'https://...' });
const controller = new systemShare.ShareController(data);
controller.on('shareCompleted', (r: systemShare.ShareOperationResult) => { /* r.targetAbilityInfo.name */ }); // 18+
await controller.show(context /* common.UIAbilityContext */, { anchor: 'shareBtnId',
previewMode: systemShare.SharePreviewMode.DEFAULT, selectionMode: systemShare.SelectionMode.SINGLE });
// 错误码:401 / 1003702001 记录类型不支持 / 1003702002 IPC 数据过大
// ⑤ 碰一碰 / 隔空传送(@kit.ShareKit · harmonyShare)
harmonyShare.on('knockShare', { windowId },
(t: harmonyShare.SharableTarget) => t.share(data)); // 离开页面必须 off
harmonyShare.on('gesturesShare', (t: harmonyShare.SharableTarget) => t.share(data)); // 建议 3 秒内
// canIUse('SystemCapability.Collaboration.HarmonyShare');宿主应用拿不到分享结果
更多推荐

所有评论(0)