ITPub博客

首页 > 数据库 > SQL on Hadoop > 技术解读:HarmonyOS 碰一碰与隔空传送实战:真正的坑在 3 秒铁律和生命周期

技术解读:HarmonyOS 碰一碰与隔空传送实战:真正的坑在 3 秒铁律和生命周期

SQL on Hadoop 作者:Flow_Official 时间:2026-09-16 10:11:35 0 删除 编辑

HarmonyOS 碰一碰与隔空传送实战:真正的坑在 3 秒铁律和生命周期

本文主线以 HarmonyOS 6.0 新增能力为切口: clarifyNonShare (6.0.2(22) 起)、手机 ↔ PC/2in1 碰一碰与沙箱接收、绑定窗口的 capability 注册(6.0.0(20) 起)。碰一碰基础能力可回溯到 5.0,文中只作铺垫,一笔带过。文中代码是为说明问题自行编写的完整示例,API 名称、枚举取值与版本号等事实性信息均标注官方出处;涉及真机传输表现的部分已注明,未做任何实测数据编造。

本文主线以 HarmonyOS 6.0 新增能力为切口: clarifyNonShare (6.0.2(22) 起)、手机 ↔ PC/2in1 碰一碰与沙箱接收、绑定窗口的 capability 注册(6.0.0(20) 起)。碰一碰基础能力可回溯到 5.0,文中只作铺垫,一笔带过。文中代码是为说明问题自行编写的完整示例,API 名称、枚举取值与版本号等事实性信息均标注官方出处;涉及真机传输表现的部分已注明,未做任何实测数据编造。

碰一碰与隔空传送全景头图
碰一碰与隔空传送全景头图

引子:一句"手掌一攥文件就飞过去",V哥翻了三天文档

用户手掌在另一台手机背面一攥,手里的文件就飞过去了;对着电脑隔空一抓,手机内容就落进 PC。

用户手掌在另一台手机背面一攥,手里的文件就飞过去了;对着电脑隔空一抓,手机内容就落进 PC。

听着像调一个系统接口的事。V哥打开 Share Kit 文档准备抄示例,第一眼就发现方向不对: 网上的代码几乎都只写 harmonyShare.on('knockShare', cb) ,却没人告诉你这俩事件到底差在哪、回调里不立刻发数据会怎样、PC 端为什么要多传一个 windowId。 这三件事,才是这篇要讲清楚的重点。

一、Share Kit 两兄弟:knockShare 与 gesturesShare 差在哪

碰一碰和隔空传送,在代码里就是两个事件名,都由 harmonyShare 抛出:

| 事件 | 触发方式 | 起始版本 | 说明 |
| --- | --- | --- | --- |
| knockShare | 设备背部轻贴(碰一碰) | 5.0.0(12) | 基础能力,5.0 时代就有 |
| gesturesShare | 空中手势(隔空传送) | 6.0.0(20) | 6.0 才开放 ,是本文的切口之一 |

这就是第一个原创判断: 想落在 6.0 以上,隔空传送( gesturesShare )本身就是现成切口 ——它是 6.0.0(20) 才开放的。碰一碰虽老,但从 6.0.0(20) 起多了一个"带窗口注册"的能力重载,手机 ↔ PC/2in1 的沙箱接收也从这个版本起可用。所以写新功能时,建议两个事件一起注册,覆盖"贴"和"抓"两种动作。

官方在指南里对碰一碰的描述很直接:当宿主应用收到系统发出的碰一碰事件回调,可能因某些原因无法发起分享时,需及时终止,避免用户长时间等待( 碰一碰内容分享 )。这句话其实是全文的纲:回调里你必须"给个说法"。

二、注册这道坎:windowId 到底要不要绑

规划表里常写"注册必须绑 windowId",但翻 API 参考会发现这事没那么绝对。 harmonyShare.on 有两种重载 :

  • 不带配置: on('knockShare', callback) —— 从 5.0.0(12) 起就有;
  • 带窗口配置: on('knockShare', capability: SendCapabilityRegistry, callback) —— 6.0.0(20) 起 新增。

SendCapabilityRegistry 是 6.0.0(20) 才出生的注册配置项,字段有俩: windowId (指定可轻贴的窗口)和 sendOnly: boolean (默认 false,双端都为 true 时双向分享被拦截, 仅碰一碰支持 )。

V哥的自创判断:手机单窗口可以不绑,PC/2in1/Tablet 必须绑。 手机一个应用基本就一个前台窗口,系统知道往哪发;但 PC 上可能开了好几个窗口,你不告诉系统"可轻贴的是主窗口",轻贴就可能落到不可见的窗口上——这正是规划表那句"不绑就等着窗口不可见时乱回调"的真实含义,只不过它不是语法报错,而是多窗口场景下的体验错乱。官方在 off 的说明里也明确写道:推荐在 PC/2in1 和 Tablet 上使用带 windowId 的注册方式。

注册绑 windowId:手机单窗口 vs PC/2in1 多窗口
注册绑 windowId:手机单窗口 vs PC/2in1 多窗口

所以V哥封装时把两种注册分开:手机走 registerOnPhone ,PC/2in1 走先取 windowId 再绑定的 registerOnWindow (见第五节代码)。

三、把数据装进 SharedData:一条链接怎么变一张卡

回调拿到的 SharableTarget 只是个"执行句柄",真正要发的东西得自己装进 systemShare.SharedData 。最常用的是链接分享,utd 类型填 utd.UniformDataType.HYPERLINK :

import
{ uniformTypeDescriptor
as
utd }
from
'@kit.ArkData'
;
import
{ systemShare, harmonyShare }
from
'@kit.ShareKit'
;
import
{ fileUri }
from
'@kit.CoreFileKit'
;
const
data =
new
systemShare.
SharedData
({
utd
: utd.
UniformDataType
.
HYPERLINK
,
content
:
'https://yourapp.example.com/video/20260910'
,
title
:
'碰一碰分享卡片标题'
,
description
:
'碰一碰分享卡片描述'
});
data.
thumbnailUri
= fileUri.
getUriFromPath
(
this
.
context
.
filesDir
+
'/poster.jpg'
);

title + description + thumbnailUri 三个字段会决定卡片模板(纯图片 / 沉浸式大卡 / 白卡上下),官方有专门的设计指南。 一个隐蔽坑 :预览图建议走沙箱 fileUri ,别直接塞 PixelMap 或外部不可读路径;云端大图来不及下载时,可以先发核心数据、后用 updateShareData 补图,避免卡在下载上超时。

链接分享真正的价值在"落地":utd 配成 general.hyperlink 后,交给 App Linking 接手(见第七节),应用没装也能跳。

四、3 秒铁律:share / clarifyNonShare / reject 三选一

这是全文最该抄进架构评审的一节。官方在最佳实践页对碰一碰的措辞是:

收到碰一碰分享事件回调后,需尽快调用 sharableTarget.share() 方法发起分享,超过 3 秒可能会失败。

收到碰一碰分享事件回调后,需尽快调用 sharableTarget.share() 方法发起分享,超过 3 秒可能会失败。

( 分享 App Linking 直达应用 )

V哥的提炼:回调里你只有三条路,且必须在窗口期内选一条——发、澄清、拒绝,缺一不可"给说法"。 clarifyNonShare 和 reject 同样是有效答复,别只在能发时才响应,异常时也得回。

| 方法 | 起始版本 | V哥什么时候用它 |
| --- | --- | --- |
| share(data) | 5.0.0(12) | 当前界面确有可分享内容,装好 SharedData 直接发 |
| clarifyNonShare({message}) | 6.0.2(22) | 界面本身不支持分享(如空白页),礼貌告知并引导去可分享页 |
| reject(errorCode) | 5.0.3(15) | 网络/下载失败等 genuinely 出错,上报错误码让用户知道原因 |

自创口诀:三秒不回话,系统直接判超时——数据要提前备好,不能等用户点了再现拼。 这意味着分享数据(链接、标题、缩略图)最好在进页面时就预备好,回调里只取不拼;任何耗时 IO(云端下载、压缩)都别堵在回调里。

3 秒三选一决策流程图
3 秒三选一决策流程图

五、异常别装死:clarifyNonShare(6.0.2 起)与 reject 的分工

clarifyNonShare 是 6.0.2(22) 才补上的"温和终止":它和 reject 的定位完全不同。

  • clarifyNonShare :当前界面"不是不能发,是不该发"——比如你在一个设置页被碰了。它带一句 message 引导用户去正确页面,体验软。官方明确它 仅支持碰一碰分享功能 (隔空传送用不了)。
  • reject :是真的出错了(下载失败、业务异常),传 SharableErrorCode 让系统弹窗说明原因,体验硬。

下面是V哥自写的封装,把注册、三秒兜底、三选一都收在一个 KnockShareController 里:

import
{ harmonyShare, systemShare }
from
'@kit.ShareKit'
;
import
{ uniformTypeDescriptor
as
utd }
from
'@kit.ArkData'
;
import
{ fileUri }
from
'@kit.CoreFileKit'
;
import
{
window
}
from
'@kit.ArkUI'
;
import
{ common }
from
'@kit.AbilityKit'
;
import
{
BusinessError
}
from
'@kit.BasicServicesKit'
;
const
RESPOND_DEADLINE_MS
=
3000
;
export
interface
SharePayload
{
link
:
string
;
title
:
string
;
description
:
string
;
  thumbPath?:
string
;
}
export
class
KnockShareController
{
private
context
: common.
UIAbilityContext
;
private
watchdog?:
number
;
constructor
(
context: common.UIAbilityContext
) {
this
.
context
= context;
  }
registerOnPhone
(
handler
:
(
t: harmonyShare.SharableTarget
) =>
void
):
void
{
const
cb
= (
t: harmonyShare.SharableTarget
) =>
this
.
guard
(handler, t);
    harmonyShare.
on
(
'knockShare'
, cb);
    harmonyShare.
on
(
'gesturesShare'
, cb);
  }
async
registerOnWindow
(
handler
:
(
t: harmonyShare.SharableTarget
) =>
void
):
Promise
<
void
> {
const
win =
await
window
.
getLastWindow
(
this
.
context
);
const
windowId = win.
getWindowProperties
().
id
;
const
cb
= (
t: harmonyShare.SharableTarget
) =>
this
.
guard
(handler, t);
    harmonyShare.
on
(
'knockShare'
, { windowId }, cb);
    harmonyShare.
on
(
'gesturesShare'
, { windowId }, cb);
  }
private
guard
(
handler
:
(
t: harmonyShare.SharableTarget
) =>
void
,
target
: harmonyShare.
SharableTarget
):
void
{
this
.
watchdog
=
setTimeout
(
() =>
{
      target.
clarifyNonShare
({
message
:
'分享准备超时,请稍后重试'
});
    },
RESPOND_DEADLINE_MS
)
as
unknown
as
number
;
handler
(target);
  }
send
(
target
: harmonyShare.
SharableTarget
,
payload
:
SharePayload
):
void
{
const
data =
new
systemShare.
SharedData
({
utd
: utd.
UniformDataType
.
HYPERLINK
,
content
: payload.
link
,
title
: payload.
title
,
description
: payload.
description
});
if
(payload.
thumbPath
) {
      data.
thumbnailUri
= fileUri.
getUriFromPath
(payload.
thumbPath
);
    }
    target.
share
(data)
      .
then
(
() =>
this
.
clearWatchdog
())
      .
catch
(
(
e: BusinessError
) =>
this
.
terminate
(target, e));
  }
private
terminate
(
target
: harmonyShare.
SharableTarget
,
e
:
BusinessError
):
void
{
this
.
clearWatchdog
();
if
(e.
code
=== harmonyShare.
SharableErrorCode
.
DOWNLOAD_ERROR
) {
      target.
reject
(harmonyShare.
SharableErrorCode
.
DOWNLOAD_ERROR
);
    }
else
{
      target.
clarifyNonShare
({
message
:
'当前内容暂不支持分享'
});
    }
  }
private
clearWatchdog
():
void
{
if
(
this
.
watchdog
!==
undefined
) {
clearTimeout
(
this
.
watchdog
);
this
.
watchdog
=
undefined
;
    }
  }
unregisterOnPhone
():
void
{
this
.
clearWatchdog
();
    harmonyShare.
off
(
'knockShare'
);
    harmonyShare.
off
(
'gesturesShare'
);
  }
async
unregisterOnWindow
():
Promise
<
void
> {
this
.
clearWatchdog
();
const
win =
await
window
.
getLastWindow
(
this
.
context
);
const
windowId = win.
getWindowProperties
().
id
;
    harmonyShare.
off
(
'knockShare'
, { windowId });
    harmonyShare.
off
(
'gesturesShare'
, { windowId });
  }
}

注意 off 时必须传和 on 一致的事件名与(PC 端的) windowId / callback ,否则注销不掉,残留监听会和其它应用的碰一碰打架。

六、手机 ↔ PC/2in1:沙箱接收与 windowId 绑定

6.0.0(20) 起,碰一碰不再只是手机对手机—— 手机 ↔ PC/2in1 也能碰,且接收端走沙箱 。这条链路是 6.0 最值得写的新切口:

  • 发送端若是 PC,必须用带 windowId 的注册(见第二节),否则系统不知道该从哪个窗口发起;
  • 接收端在应用沙箱里拿到文件/链接 URI,落盘或预览都在自己目录内,不用额外开权限;
  • 文件分享时 content 要用 fileUri.getUriFromPath 把沙箱路径转成 URI,别塞绝对路径或外部不可读路径。

传输表现(速率、稳定性、机型差异)V哥没法在这里编—— 以真机实测为准 。文档明确这类近场能力要求双端都具备 SystemCapability.Collaboration.HarmonyShare ,且建议双方登录同一华为账号或已在信任设备列表。

手机与 PC/2in1 碰一碰沙箱接收流程
手机与 PC/2in1 碰一碰沙箱接收流程

七、没装 App 也能落地:App Linking 兜底

链接分享的最后一公里是"对端没装你的应用怎么办"。官方给的方案是 App Linking:

  • 应用已装:App Linking 直接拉起,落地到具体页;
  • 应用没装:默认走系统浏览器开网页,配合直达应用市场能力可跳应用市场,安装后靠延迟链接仍能还原之前分享的内容。

所以 content 里那条链接别随便写——它既是分享内容,也是落地路由。配上 module.json5 里的 skills (scheme 用 https、配 entity.system.browsable 、 domainVerify: true ),端到端才算通。

上线自检清单

  • 切口认清楚了?隔空传送 gesturesShare 仅在 6.0.0(20)+ 可用, clarifyNonShare 仅在 6.0.2(22)+ 可用,低版本要降级处理吗?
  • 两个事件都注册了吗? knockShare + gesturesShare 一起覆盖"贴"和"抓"。
  • 手机单窗口用无 capability 重载;PC/2in1/Tablet 用带 windowId 的 SendCapabilityRegistry 重载了吗?
  • 分享数据(链接、标题、缩略图)进页面时就预备好了吗?回调里只取不拼,没在回调里做耗时 IO 吧?
  • 回调里三条路都接了? share / clarifyNonShare / reject 任一异常都有答复,没有"装死"分支?
  • clarifyNonShare 只在碰一碰用、隔空传送不用,这个边界分清了吗?
  • reject 传的是 SharableErrorCode 枚举而不是随便一个数字吗?
  • off 时传了和 on 一致的事件名与 windowId / callback 吗?页面销毁、退后台都注销了吗?
  • 链接分享的 utd 是 general.hyperlink 、且配了 App Linking 兜底(已装/未装两条路径都验证过)吗?
  • 文件分享的 content 用 fileUri.getUriFromPath 转沙箱 URI 了吗?没塞绝对路径吧?
  • 预览图走沙箱 fileUri 、超大云端图用了 updateShareData 延迟补图吗?
  • 双端都具备 SystemCapability.Collaboration.HarmonyShare 、账号/信任设备条件在真机验证过了吗?传输速率与稳定性 以真机实测为准 。

参考与出处

本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档,文中的结构、代码示例、决策流程与自检清单为本人整理编写:

  • 碰一碰分享(开发指南)
  • 隔空传送(开发指南)
  • 手机与手机碰一碰内容分享
  • harmonyShare(华为分享)ArkTS API 参考
  • 碰一碰链接分享(最佳实践)
  • 分享 App Linking 直达应用(含 3 秒超时表述)
  • 使用 App Linking 实现应用间跳转
  • 鸿蒙接入"碰一碰"和"隔空传送"教学(blog)
  • HarmonyOS 跨设备分享教程:隔空传送与碰一碰分享(blog,含 3 秒流程)

最后一句 :这两个能力真正的难点不在"注册"那一行,而在回调之后那 3 秒——你得提前把数据备好、把异常分支接全、把生命周期对齐。想清楚这三件事,剩下的就是 on 一下、 share 一下。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:Juejin HarmonyOS

原创地址:https://juejin.cn/post/7685648064544292910

原文作者:威哥爱编程;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-08-02

  • 博文量
    36
  • 访问量
    32565