先说一个场景

我在做一个摘录应用:读到一句好话,存下来,卡片上有书名、作者、页码。

以前的分享流程是这样的:点分享、等面板弹出、找应用、找联系人、点发送,对方再点开链接。六步。

现在想做的事很简单:两台手机顶部轻碰一下,这句摘录就过去了。没有“选择应用”,没有“选择联系人”,轻碰本身就是发送动作。

这就是 Share Kit 的碰一碰分享。这篇文章把发送端从注册到发送完整走一遍,再讲清楚接收端的两条路,以及几个只有真上手才会遇到的问题。

碰一碰就绪:能力支持,监听已注册

碰一碰到底是谁在工作

先说清楚分工,后面的代码才好理解。

找设备、连通道、传数据,全部由系统完成。应用要做的事只有两件:在合适的页面注册,在回调里交出要分享的数据。

所以整个接入过程没有蓝牙、没有局域网发现、没有连接管理。你的代码里不会出现“搜索附近设备”这种逻辑,也不需要处理配对。系统判断两台设备碰上了,就把回调打到前台应用上。

这也是它和“隔空传送”最大的区别:隔空传送是用户做一个手势,碰一碰是两台设备物理接触。两者在代码层都用 harmonyShare,但注册的事件名不同。

第一步:在页面里注册

碰一碰不是全局开关,它跟着页面走。进入一个“有东西可分享”的页面就注册,离开就注销。

import { harmonyShare, systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';

aboutToAppear(): void {
  const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
  this.supported = canIUse('SystemCapability.Collaboration.HarmonyShare');
  if (!this.supported) {
    this.statusText = '这台设备不支持碰一碰';
    return;
  }
  window.getLastWindow(ctx).then((w: window.Window) => {
    this.win = w;
    this.windowId = w.getWindowProperties().id;
    this.startListen();
  });
}

onPageShow(): void {
  if (this.windowId >= 0) {
    this.startListen();
  }
}

onPageHide(): void {
  this.stopListen();
}

注册和注销都带同一个 windowId:

private cap(): harmonyShare.SendCapabilityRegistry {
  return { windowId: this.windowId, sendOnly: true };
}

private startListen(): void {
  if (!this.supported || this.windowId < 0 || this.listening) {
    return;
  }
  harmonyShare.on('knockShare', this.cap(), this.onKnock);
  this.listening = true;
}

private stopListen(): void {
  if (!this.listening) {
    return;
  }
  harmonyShare.off('knockShare', this.cap());
  this.listening = false;
}

三个细节:

windowId 要取当前窗口的。 碰一碰在 UI 上要挂到具体窗口上,多窗口场景下用错 id,轻碰可能没有反应。用 getWindowProperties().id 拿,别自己编一个。

sendOnly 表明这个页面只发不收。 设成 true 之后,本端只负责发送;两端都设 true 时没法双向分享。摘录页只把内容送出去,所以设 true。

注册要幂等。 onPageShow 和 aboutToAppear 都会尝试注册,用一个 listening 标记挡住重复调用,避免同一窗口挂上多份监听。

第二步:轻碰之后,回调里组装数据

轻碰发生的那一刻,系统回调你注册的函数,参数是一个 SharableTarget。你要做的就是构造数据、交出去。

private onKnock = (target: harmonyShare.SharableTarget): void => {
  if (!this.thumbReady) {
    target.clarifyNonShare({ message: '摘录还没准备好,请稍后再碰一次' });
    return;
  }
  const data: systemShare.SharedData = new systemShare.SharedData({
    utd: utd.UniformDataType.PLAIN_TEXT,
    content: '把事讲清楚,比把话说漂亮更难。',
    title: '《置身事内》摘录',
    description: '兰小欢 · 经济',
    thumbnailUri: this.thumbUri
  });
  target.share(data).then(() => {
    this.sent += 1;
    this.lastEvent = '已交给系统,结果由系统通知告知';
  }).catch((err: BusinessError) => {
    this.lastEvent = `发送失败:${err.code}`;
  });
};

utd 是统一数据类型。纯文本用 general.plain-text,链接用 general.hyperlink。类型选错,对端可能拿到一堆看不懂的东西,也不会出现“用哪个应用打开”的推荐。

content 是真正的内容,title、description、thumbnailUri 是给对方看的卡片信息。这四个字段的分工,下一节单独讲。

这里有一个要提前接受的设定:share() 的成功只代表数据交给了系统,不代表对方收到了。 官方说得很明确,宿主应用无法获得分享结果,接收或拒绝会通过系统通知告知用户。所以代码里不要写“发送成功”的弹窗,那是在撒谎。

注册 → 轻碰 → 组装 → 发送,以及两条异常分支

异常分支也别省。页面确实没有可分享内容时,调 clarifyNonShare({ message })(6.0.2(22) 起),告诉用户换个页面再碰;内容还在准备、下载失败时,调 reject() 终止这次分享,别让对端一直等。

发送的不是一条消息,是一张卡片

对端不会收到一行“某某分享了文本”,而是一张有预览图的卡片。卡片长什么样,由你传了哪些字段决定。

同一份数据,三种卡片

  • 纯图片:只传 thumbnailUri,整张卡就是预览图,适合分享图片、文件。
  • 沉浸式大卡:传了 title、description 和预览图,且预览图宽高比小于 1:1。图片铺在上面,标题描述压在下方。
  • 白卡上下:同样三个字段,但预览图宽高比大于 1:1,图片在上、文字在下。

我这个 demo 的预览图是 600×800,比例 3:4,小于 1:1,所以走的是沉浸式大卡。标题最多两行、描述只有一行,超过就省略号,所以标题控制在 20 个汉字左右,描述写最重要的那一句。

预览图有明确的建议值:3:4,最小 600×800,最大 3000×4000,超过 512KB 会被系统压缩。 别直接塞一张手机原图,加载慢,显示出来还糊。

如果预览图在云端、回调触发时还没下载完,可以先 share() 建立连接,等图片到了再调 updateShareData({ thumbnailUri }) 补图。这条能力 6.0.0(20) 就有,专治“图片慢半拍”。

接收端:两条路,选一条

发送端把数据交出去之后,接收端怎么“打开”这件事,取决于你选哪种链接方案。

方案应用已安装应用未安装
App Linking直接拉起应用内页面打开浏览器;配合直达应用市场可跳商店
Deep Linking直接拉起应用内页面提示“暂无可用打开方式”

要做“碰一下就直达应用”,选 App Linking:先在 AGC 开通服务,在域名服务器放校验文件,再在目标应用的 module.json5 里声明关联域名。

"skills": [
  {
    "entities": ["entity.system.browsable"],
    "actions": ["ohos.want.action.viewData"],
    "uris": [
      { "scheme": "https", "host": "your.domain.com", "pathPrefix": "/note" }
    ]
  }
]

注意这里必须是真的域名,并且和 AGC 里配置的一致,否则校验不过,轻碰后直接跳浏览器。发送数据时,utd 要配成 general.hyperlink,Share Kit 才会按链接处理。

还有一种更轻的做法:用 ShareExtensionAbility 做一个分享详情页,让应用出现在分享面板的推荐区。这条路不依赖域名,但用户看到的是“选择应用”而不是“直达页面”。

接收端这次没有做。 我手上没有可用的域名,也没做 AGC 配置,上面这段是照着官方文档写的示意,不是跑通的结果。这一点我在下面“验证到哪一步”里也会再说一次。

生命周期:页面在,才叫“能碰”

碰一碰的注册是跟页面绑的,这带来一个很实际的问题:用户在别的页面轻碰,你的应用也收到回调,那你得判断“当前页面有没有东西可分享”。

最省事的做法是只在可分享页面注册,离开就注销。我用 onPageShow / onPageHide 控制,同时用面板上的按钮手动暂停,方便验证注册和注销真的生效。

暂停监听:状态、按钮、能力行同时变化

点一下“暂停监听”,标题从“已就绪”变“未监听”,状态行从“已注册”变“未注册”,按钮变成“重新监听”;再点一次又回到就绪。截图里的 #824 是当前窗口 id,调试时很有用——多窗口或者分屏场景下,能一眼确认注册到哪个窗口上。

真机上这一步是可以单机验证的:canIUse 返回 true、on() 不抛异常、状态跟着切换。但“轻碰回调真的会来”这件事,单机验证不了,必须有第二台设备。

六个容易踩的点

  1. 把 share() 当发送成功。 它只代表数据交给了系统。对端收没收到,宿主不知道,只有系统通知。UI 上别写“已送达”。
  2. 忘了双端条件。 两台设备都要亮屏、解锁,并且开启华为分享(系统默认开启)。任意一端不支持碰一碰,轻碰没有任何反应,也不会报错。
  3. windowId 写死。 文档示例里的 999 只是占位。真机上必须取当前窗口 id,否则回调不会来。
  4. 没有内容却不拒绝。 页面空着就轻碰,对端会一直等。该 clarifyNonShare 就调,该 reject 就调。
  5. 预览图随手上原图。 几 MB 的图会被压缩,加载还慢。按 3:4、512KB 以内的标准出图。
  6. 引导文案写错。 官方建议的提示是“可碰一碰分享至 HarmonyOS 5 及以上版本手机”;动图引导资源在官方文档里提供了 knock_share_guide 目录,放进 rawfile 就能用。这些细节决定用户敢不敢碰。

还有一条容易误解的:手机碰手机没有坐标。 getInfo() 返回的坐标只在“手机与 PC / 2in1 / 平板”碰一碰时才有效(26.0.0 起),给的是以屏幕左上角为原点的 px 坐标。想做“碰在窗口哪个位置就落到哪个位置”的精准分享,那是大屏场景的能力,手机对手机用不上。

验证到哪一步

把边界写清楚:

  • 已验证:本机支持碰一碰能力(canIUse 返回 true);按当前窗口 id 注册成功;暂停 / 恢复监听后,状态、按钮、信息行同步变化;预览图从 rawfile 拷贝到沙箱并生成 file URI 正常显示。
  • 未验证:两台设备之间的真实轻碰;轻碰回调触发后的 share();对端接收与打开;App Linking 直达应用。
  • 原因:这次只用一台设备做验证,没有接入第二台;接收端需要域名和 AGC 配置,这两样我都没有。

按文章的规矩,能跑的写实测,没跑的写“没验证”。这篇的代码是完整的,缺的是两台设备和一套域名。

总结

碰一碰分享的代码量很小:一个 on('knockShare') 注册,一个回调里组装 SharedData,一个 share() 交出去。难的不是接口,是三件事——在哪注册(跟着页面走)、交什么数据(决定对方的卡片长什么样)、怎么接住(App Linking 还是分享面板)。

它最适合“一句话、一张图、一个文件”这种轻量、即时、面对面的分享:摘录、联系人、Wi-Fi 密码、刚拍的照片。真要做批量传输或者后台自动同步,那是另一套东西。

最后提醒一句:碰一碰的体验有一半不在代码里。双端亮屏解锁、华为分享开关、引导文案、预览图质量,任何一环掉链子,用户感受到的都是“碰了没反应”。

参考地址

  1. https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/knock-share-between-phones-overview
  2. https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/knock-share-between-phones-content
  3. https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-harmony-share
  4. https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-system-share
  5. https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-knock-file-share
  6. https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-share-7
  7. https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-share-11
Logo

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

更多推荐