# 拍的照片碰一下就落到那位嘉宾名下 # 智绘鸿蒙·如 7 而至#

## 前言

会务组提的需求：圆桌会议上大家各自拍照片、记笔记，散会时把自己那份"碰一下"递给对面的嘉宾，不用加微信、不用传群、不用点名选接收人。

这就是 Share Kit 的碰一碰分享。API 只有三行：注册回调、回调里给数据、系统把内容送过去。

我带你把发送侧完整写一遍。**但请先记住这篇最重要的一句话：碰一碰的注册接口"注册即成功"，它不会告诉你这台设备根本没有 NFC。** 我在一台平板上实测，四次调用全部无异常，回调触发次数是 0。所以这篇的后半段会告诉你，哪些部分必须两台设备、两次真人才测得出来。

## 第 1 步：先探能力，别先写代码

新建工程后第一件事，把这几项系统能力打出来：

```typescript
const share: boolean = canIUse('SystemCapability.Collaboration.HarmonyShare');
const sysShare: boolean = canIUse('SystemCapability.Collaboration.SystemShare');
const nfc: boolean = canIUse('SystemCapability.Communication.NFC.Core');
const collab: boolean = canIUse('SystemCapability.Application.DeviceCollaboration');
```

MatePad Pro（HarmonyOS 7 / API 26）的结果：

```
HarmonyShare=true  SystemShare=true  NFC.Core=false  DeviceCollaboration=false
```

前两个 true 说明**接口在**，后两个 false 说明**这台机器碰不了**。

这一步为什么关键：`SystemCapability.Communication.NFC.Core` 为 false 时，后面所有注册调用依然全部成功。你如果只看"有没有抛异常"来判断功能是否可用，一定会写出一个静默无效的发布版本。

正确的自检写法是：**把 `NFC.Core` 当成开关，为 false 就在界面上直接显示"本设备不支持碰一碰"，而不是等用户去碰。**

## 第 2 步：注册发送回调

```typescript
private onKnock: (target: harmonyShare.SharableTarget) => void =
  (target: harmonyShare.SharableTarget) => {
    // 系统检测到一次碰一碰，把 target 交给你
    target.share(this.buildData());
  };

aboutToAppear(): void {
  harmonyShare.on('knockShare', this.onKnock);
}

aboutToDisappear(): void {
  harmonyShare.off('knockShare', this.onKnock);
}
```

三个必须注意的点：

1. **回调引用要存成成员变量。**`off` 要传同一个函数引用，写成匿名函数就注销不掉，页面反复进出会叠加回调。
2. **注册时机放 `aboutToAppear`，注销放 `aboutToDisappear`。**碰一碰是前台行为，页面已经销毁还挂着回调，会在不该出现的时候把这一页的数据分享出去。
3. `on` 还有第二个重载 `on('knockShare', capability: SendCapabilityRegistry, callback)`，带窗口注册表，用于多窗口场景精确指定是哪个窗口发起分享（26.0.0 起电脑设备支持精准碰一碰）。单窗口应用可以先不碰它。

![能力探测结果：接口在、NFC 不在](assets/01-registered.png)

## 第 3 步：在回调里给数据

`SharableTarget` 上有四个出口，用错会导致对端表现很奇怪：

| 方法 | 什么时候用 |
| --- | --- |
| `share(data: systemShare.SharedData)` | 正常分享 |
| `reject(error: SharableErrorCode)` | 当前状态下不该分享（比如没选中照片），让系统给出提示 |
| `clarifyNonShare(info)` | 明确告诉系统这次不算分享，静默取消 |
| `updateShareData(info)` | 内容变了，更新将要分享的数据 |
| `getInfo()` | 拿对端设备信息，用于埋点 |

`SharedData` 的构造里 `utd` 是必填的（Uniform Type Descriptor），文本用 `'text/plain'`：

```typescript
new systemShare.SharedData({
  utd: 'text/plain',
  content: '议题：端侧模型上线节奏\n结论：先灰度 10%，下周三给数据\n负责人：张三',
  title: '参会笔记 · 端侧模型评审',
  description: `由「碰记」在 19:07 导出`
});
```

图片/文件类内容要传 `uri`，并且这个 uri 必须是应用能访问到的沙箱路径或授权过的 uri——直接传相册 uri 过去，对端拿不到文件，表现就是"碰了但对方什么都没收到"。这是官方 FAQ 里"暂无可用打开方式"的常见成因之一。

## 第 4 步：接收侧注册

要让本机能收，得注册 `dataReceive`，并声明能接哪些类型：

```typescript
const reg: harmonyShare.RecvCapabilityRegistry = {
  windowId: 0,
  capabilities: [{ utd: 'text/plain', maxSupportedCount: 4 }]
};
harmonyShare.on('dataReceive', reg, (t: harmonyShare.ReceivableTarget) => {
  // t.getInfo() 里拿到内容
});
```

**这里有个坑我踩给你看**：上面这段 `windowId` 我故意传了 `0`（明显不是真实窗口 id），结果照样返回"注册成功"，不抛异常：

```
on(dataReceive) 注册成功（windowId 传了 0）
```

也就是说参数不做校验。真实窗口 id 要从 `window` 模块拿（`windowClass.windowProperties.id` 或 `getWindowProperties().id`）。传错的表现是"注册成功但永远收不到"，和没注册完全一样，排查成本极高。

![六次调用全部无异常，回调触发 0 次](assets/02-log.png)

## 第 5 步：把没测到的部分说清楚

我这轮只连了一台设备，所以以下这些**没有数据**，不编：

- 真实对碰时 `onKnock` 到底什么时候被调、`getInfo()` 里有哪些字段；
- `target.share()` 之后对端的落地形态（是通知、是打开应用、还是进分享面板）；
- 未注册时碰一碰的系统提示文案；
- 一次碰多张图片（`maxSupportedCount` 的实际生效上限）；
- 两台设备登录不同华为账号时的权限表现。

这些必须两台 NFC 设备 + 真人对碰才能验。写教程到这一步，诚实的做法就是把清单交出去，而不是把"应该可以"写成"已验证"。

## 实测数据

单台 MatePad Pro，按顺序点五个动作：

| 动作 | 结果 | 是否抛异常 |
| --- | --- | --- |
| 探测四项 syscap | HarmonyShare=true / SystemShare=true / NFC.Core=false / DeviceCollaboration=false | 否 |
| `on('knockShare')` | 注册成功 | 否 |
| 再 `on('knockShare')` 一次 | 也成功，无提示 | 否 |
| `on('dataReceive', {windowId:0,...})` | 注册成功 | 否 |
| `off('knockShare')` | 完成 | 否 |
| 重新 `on('knockShare')` | 注册成功 | 否 |
| 回调实际触发次数 | **0** | — |

结论一句话：**六个调用全绿，功能一次都没生效。** 这就是"靠返回值做自检"的失败模式。

## 上线前请做完这四件事

1. 界面上显式区分"接口可用"和"设备支持"：`NFC.Core=false` 时直接禁用碰一碰入口。
2. `windowId` 用真实值，并在注册后把它打一行日志，出问题时第一眼就能看到。
3. 分享出去的内容做本地留痕（谁、什么时候、碰给了哪台），否则用户报"我没收到"时你无从对账。
4. 两台真机、两个账号、正反向各碰一次，作为发布卡点，而不是开发自测项。

## 总结

碰一碰适合"临时、一次性、身边人"的内容交接：会议笔记、现场照片、展台资料、同学之间传一道题。它的价值在于省掉"找联系人—发出去—对方点开"这一串，所以内容要小、要即时。

不适合大文件、需要留痕审计的场景，也不适合设备形态里没有 NFC 的机器（部分平板就没有）。后者不是降级能解决的，只能换交互。

> 💡 **一句话**：`harmonyShare.on()` 在没有 NFC 的设备上也会返回成功——判断能不能碰，只能靠 `canIUse('SystemCapability.Communication.NFC.Core')`，不能靠注册有没有报错。

## 参考文献

1. 碰一碰文件分享（最佳实践）：https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-knock-file-share
2. 碰一碰链接分享（最佳实践）：https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-knock-video-share
3. harmonyShare（华为分享）API 参考：https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-harmony-share
4. Share Kit 简介：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/share-introduction
