【鸿蒙开发实战】HarmonyOS 跨设备分享教程:隔空传送与碰一碰分享

HarmonyOS 跨设备分享教程:隔空传送与碰一碰分享

一、概述

HarmonyOS Share Kit 提供了两种无接触/近场跨设备分享能力,允许应用在不拉起系统分享面板的情况下,直接将内容推送到对端设备:

能力 事件名 触发方式 适用场景
隔空传送 gesturesShare 手势感应(设备举至对端屏幕前方 20~40cm,握拳抓取) 跨设备推送图片、链接、配置
碰一碰分享 knockShare 两台设备物理轻触 近场名片、文件互传

本教程以「云星图」项目为实战案例,讲解三种典型用法:

  1. 隔空手势触发上传 -- 手势不传数据,而是触发本机批量上传(NormalUpLoad
  2. 隔空/碰一碰分享配置链接 -- 将图床配置以 AppLinking 推送到对端(CloudSetting
  3. 隔空/碰一碰分享图片 -- 将当前查看的云端图片推送到对端设备(PicView

整体能力架构如下:
#mermaid-svg-JRMRzg5WgDQAStdz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JRMRzg5WgDQAStdz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JRMRzg5WgDQAStdz .error-icon{fill:#552222;}#mermaid-svg-JRMRzg5WgDQAStdz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JRMRzg5WgDQAStdz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JRMRzg5WgDQAStdz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JRMRzg5WgDQAStdz .marker.cross{stroke:#333333;}#mermaid-svg-JRMRzg5WgDQAStdz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JRMRzg5WgDQAStdz p{margin:0;}#mermaid-svg-JRMRzg5WgDQAStdz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JRMRzg5WgDQAStdz .cluster-label text{fill:#333;}#mermaid-svg-JRMRzg5WgDQAStdz .cluster-label span{color:#333;}#mermaid-svg-JRMRzg5WgDQAStdz .cluster-label span p{background-color:transparent;}#mermaid-svg-JRMRzg5WgDQAStdz .label text,#mermaid-svg-JRMRzg5WgDQAStdz span{fill:#333;color:#333;}#mermaid-svg-JRMRzg5WgDQAStdz .node rect,#mermaid-svg-JRMRzg5WgDQAStdz .node circle,#mermaid-svg-JRMRzg5WgDQAStdz .node ellipse,#mermaid-svg-JRMRzg5WgDQAStdz .node polygon,#mermaid-svg-JRMRzg5WgDQAStdz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JRMRzg5WgDQAStdz .rough-node .label text,#mermaid-svg-JRMRzg5WgDQAStdz .node .label text,#mermaid-svg-JRMRzg5WgDQAStdz .image-shape .label,#mermaid-svg-JRMRzg5WgDQAStdz .icon-shape .label{text-anchor:middle;}#mermaid-svg-JRMRzg5WgDQAStdz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JRMRzg5WgDQAStdz .rough-node .label,#mermaid-svg-JRMRzg5WgDQAStdz .node .label,#mermaid-svg-JRMRzg5WgDQAStdz .image-shape .label,#mermaid-svg-JRMRzg5WgDQAStdz .icon-shape .label{text-align:center;}#mermaid-svg-JRMRzg5WgDQAStdz .node.clickable{cursor:pointer;}#mermaid-svg-JRMRzg5WgDQAStdz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JRMRzg5WgDQAStdz .arrowheadPath{fill:#333333;}#mermaid-svg-JRMRzg5WgDQAStdz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JRMRzg5WgDQAStdz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JRMRzg5WgDQAStdz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JRMRzg5WgDQAStdz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JRMRzg5WgDQAStdz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JRMRzg5WgDQAStdz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JRMRzg5WgDQAStdz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JRMRzg5WgDQAStdz .cluster text{fill:#333;}#mermaid-svg-JRMRzg5WgDQAStdz .cluster span{color:#333;}#mermaid-svg-JRMRzg5WgDQAStdz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JRMRzg5WgDQAStdz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JRMRzg5WgDQAStdz rect.text{fill:none;stroke-width:0;}#mermaid-svg-JRMRzg5WgDQAStdz .icon-shape,#mermaid-svg-JRMRzg5WgDQAStdz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JRMRzg5WgDQAStdz .icon-shape p,#mermaid-svg-JRMRzg5WgDQAStdz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JRMRzg5WgDQAStdz .icon-shape .label rect,#mermaid-svg-JRMRzg5WgDQAStdz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JRMRzg5WgDQAStdz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JRMRzg5WgDQAStdz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JRMRzg5WgDQAStdz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 三种实战场景
管理器封装(common/basic)
HarmonyOS Share Kit
gesturesShare

隔空传送事件
knockShare

碰一碰事件
GestureShareManager

enable / disable
KnockManager

enable / disable
场景一:隔空触发上传

NormalUpLoad.ets

手势 -> 本机批量上传
场景二:分享配置链接

CloudSetting.ets

配置 -> AppLinking -> 推送
场景三:分享图片

PicView.ets

云端图片 -> 下载 -> 推送

二、前置知识

2.1 能力边界与运行要求

条件 要求
系统版本 HarmonyOS 5.0 及以上
硬件能力 两台设备均需支持 SystemCapability.Collaboration.HarmonyShare
账号条件 双方登录同一华为账号,或已在「信任设备」列表中互相添加
权限 无需额外权限,系统统一管控设备发现与连接

2.2 核心对象关系

复制代码
harmonyShare.on('gesturesShare'/'knockShare', { windowId }, (target) => {
    // target: SharableTarget -- 本次传送的执行句柄
    // 3 秒内必须调用以下方法之一,否则系统判定超时
    target.share(shareData)              // 发起传送
    target.clarifyNonShare({ message })  // 当前无可分享内容
    target.reject(errorCode)             // 主动终止
})

手势/碰一碰触发后的处理流程:
#mermaid-svg-c8VT2DZQPZkBrkzW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c8VT2DZQPZkBrkzW .error-icon{fill:#552222;}#mermaid-svg-c8VT2DZQPZkBrkzW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c8VT2DZQPZkBrkzW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c8VT2DZQPZkBrkzW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c8VT2DZQPZkBrkzW .marker.cross{stroke:#333333;}#mermaid-svg-c8VT2DZQPZkBrkzW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c8VT2DZQPZkBrkzW p{margin:0;}#mermaid-svg-c8VT2DZQPZkBrkzW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster-label text{fill:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster-label span{color:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster-label span p{background-color:transparent;}#mermaid-svg-c8VT2DZQPZkBrkzW .label text,#mermaid-svg-c8VT2DZQPZkBrkzW span{fill:#333;color:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW .node rect,#mermaid-svg-c8VT2DZQPZkBrkzW .node circle,#mermaid-svg-c8VT2DZQPZkBrkzW .node ellipse,#mermaid-svg-c8VT2DZQPZkBrkzW .node polygon,#mermaid-svg-c8VT2DZQPZkBrkzW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c8VT2DZQPZkBrkzW .rough-node .label text,#mermaid-svg-c8VT2DZQPZkBrkzW .node .label text,#mermaid-svg-c8VT2DZQPZkBrkzW .image-shape .label,#mermaid-svg-c8VT2DZQPZkBrkzW .icon-shape .label{text-anchor:middle;}#mermaid-svg-c8VT2DZQPZkBrkzW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c8VT2DZQPZkBrkzW .rough-node .label,#mermaid-svg-c8VT2DZQPZkBrkzW .node .label,#mermaid-svg-c8VT2DZQPZkBrkzW .image-shape .label,#mermaid-svg-c8VT2DZQPZkBrkzW .icon-shape .label{text-align:center;}#mermaid-svg-c8VT2DZQPZkBrkzW .node.clickable{cursor:pointer;}#mermaid-svg-c8VT2DZQPZkBrkzW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c8VT2DZQPZkBrkzW .arrowheadPath{fill:#333333;}#mermaid-svg-c8VT2DZQPZkBrkzW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c8VT2DZQPZkBrkzW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c8VT2DZQPZkBrkzW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c8VT2DZQPZkBrkzW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c8VT2DZQPZkBrkzW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c8VT2DZQPZkBrkzW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster text{fill:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW .cluster span{color:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c8VT2DZQPZkBrkzW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c8VT2DZQPZkBrkzW rect.text{fill:none;stroke-width:0;}#mermaid-svg-c8VT2DZQPZkBrkzW .icon-shape,#mermaid-svg-c8VT2DZQPZkBrkzW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c8VT2DZQPZkBrkzW .icon-shape p,#mermaid-svg-c8VT2DZQPZkBrkzW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c8VT2DZQPZkBrkzW .icon-shape .label rect,#mermaid-svg-c8VT2DZQPZkBrkzW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c8VT2DZQPZkBrkzW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c8VT2DZQPZkBrkzW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c8VT2DZQPZkBrkzW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是: target.share(data)
是: target.clarifyNonShare
是: target.reject
否: 超时
系统检测到手势/碰一碰
回调触发

传入 target: SharableTarget
3 秒内是否调用

target 方法?
发起传送

数据推送到对端
提示无可分享内容

本次不传数据
主动终止

上报错误码
系统判定超时

本次传送自动失败

2.3 依赖导入

typescript 复制代码
import { harmonyShare, systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

三、封装分享管理器

两种分享方式的监听注册/注销逻辑几乎一致,区别仅在于事件名(gesturesShare vs knockShare)。下面分别封装为静态管理器,供多个页面复用。

管理器的 enable / disable 内部流程:
#mermaid-svg-3CJVXwdUmaZ9xDaQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .error-icon{fill:#552222;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .marker.cross{stroke:#333333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ p{margin:0;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster-label text{fill:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster-label span{color:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster-label span p{background-color:transparent;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .label text,#mermaid-svg-3CJVXwdUmaZ9xDaQ span{fill:#333;color:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .node rect,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node circle,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node ellipse,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node polygon,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .rough-node .label text,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node .label text,#mermaid-svg-3CJVXwdUmaZ9xDaQ .image-shape .label,#mermaid-svg-3CJVXwdUmaZ9xDaQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .rough-node .label,#mermaid-svg-3CJVXwdUmaZ9xDaQ .node .label,#mermaid-svg-3CJVXwdUmaZ9xDaQ .image-shape .label,#mermaid-svg-3CJVXwdUmaZ9xDaQ .icon-shape .label{text-align:center;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .node.clickable{cursor:pointer;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .arrowheadPath{fill:#333333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3CJVXwdUmaZ9xDaQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3CJVXwdUmaZ9xDaQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster text{fill:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .cluster span{color:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3CJVXwdUmaZ9xDaQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .icon-shape,#mermaid-svg-3CJVXwdUmaZ9xDaQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .icon-shape p,#mermaid-svg-3CJVXwdUmaZ9xDaQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .icon-shape .label rect,#mermaid-svg-3CJVXwdUmaZ9xDaQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3CJVXwdUmaZ9xDaQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3CJVXwdUmaZ9xDaQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3CJVXwdUmaZ9xDaQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} disable()


isRegistered?
直接返回
harmonyShare.off

用同一 windowId 注销
清空 windowId / callback
isRegistered = false
enable(context, callback)




canIUse 能力检测
支持?
直接返回
已注册?
先 off 注销旧监听
window.getLastWindow

获取窗口 ID
harmonyShare.on

绑定 windowId 注册监听
isRegistered = true

3.1 隔空传送管理器

核心职责:能力检测 -> 获取窗口 ID -> 注册/注销监听 -> 回调分发。

typescript 复制代码
// GestureShareManager.ets
import { harmonyShare } from '@kit.ShareKit';
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export interface GestureShareCallback {
  onGestureDetected?: (target: harmonyShare.SharableTarget) => void;
  onError?: (error: BusinessError) => void;
}

export class GestureShareManager {
  private static isRegistered: boolean = false;
  private static windowId: number | null = null;
  private static callback: GestureShareCallback | null = null;

  static enable(context: common.UIAbilityContext, callback: GestureShareCallback): void {
    // 1. 能力检测:不支持直接返回
    if (!canIUse('SystemCapability.Collaboration.HarmonyShare')) {
      return;
    }

    // 2. 已注册则先注销再重新注册,确保回调更新为最新
    if (GestureShareManager.isRegistered) {
      harmonyShare.off('gesturesShare', { windowId: GestureShareManager.windowId! });
      GestureShareManager.isRegistered = false;
    }

    GestureShareManager.callback = callback;

    // 3. 获取当前窗口 ID,绑定监听到具体窗口
    window.getLastWindow(context).then((windowClass) => {
      GestureShareManager.windowId = windowClass.getWindowProperties().id;
      harmonyShare.on('gesturesShare', { windowId: GestureShareManager.windowId }, (target) => {
        if (GestureShareManager.callback?.onGestureDetected) {
          GestureShareManager.callback.onGestureDetected(target);
        }
      });
      GestureShareManager.isRegistered = true;
    }).catch((err: BusinessError) => {
      if (GestureShareManager.callback?.onError) {
        GestureShareManager.callback.onError(err);
      }
    });
  }

  static disable(): void {
    if (!GestureShareManager.isRegistered || GestureShareManager.windowId === null) {
      return;
    }
    harmonyShare.off('gesturesShare', { windowId: GestureShareManager.windowId });
    GestureShareManager.isRegistered = false;
    GestureShareManager.windowId = null;
    GestureShareManager.callback = null;
  }

  static isEnabled(): boolean {
    return GestureShareManager.isRegistered;
  }
}

关键设计点:

  • 窗口绑定harmonyShare.on 的第二个参数是 { windowId },必须传入当前窗口 ID。这是因为系统需要知道哪个窗口在监听手势,以便在窗口不可见时自动暂停。
  • 先 off 再 onenable 被多次调用时(如 Tab 切回页面),先注销旧监听再注册新回调,避免回调过期。
  • 静态单例 :全应用只需一个监听实例,页面切换时通过 enable/disable 切换回调即可。

3.2 碰一碰管理器

结构与隔空传送完全一致,仅事件名不同:

typescript 复制代码
// KnockController.ets
export class KnockManager {
  private static isRegistered: boolean = false;
  private static windowId: number | null = null;
  private static callback: KnockShareCallback | null = null;

  static enable(context: common.UIAbilityContext, callback: KnockShareCallback): void {
    if (!canIUse('SystemCapability.Collaboration.HarmonyShare')) {
      return;
    }
    if (KnockManager.isRegistered) {
      return; // 碰一碰无需重复注册
    }
    KnockManager.callback = callback;
    window.getLastWindow(context).then((windowClass) => {
      KnockManager.windowId = windowClass.getWindowProperties().id;
      harmonyShare.on('knockShare', { windowId: KnockManager.windowId }, (target) => {
        if (KnockManager.callback?.onKnockDetected) {
          KnockManager.callback.onKnockDetected(target);
        }
      });
      KnockManager.isRegistered = true;
    }).catch((err: BusinessError) => {
      if (KnockManager.callback?.onError) {
        KnockManager.callback.onError(err);
      }
    });
  }

  static disable(): void {
    if (!KnockManager.isRegistered || KnockManager.windowId === null) return;
    harmonyShare.off('knockShare', { windowId: KnockManager.windowId });
    KnockManager.isRegistered = false;
    KnockManager.windowId = null;
    KnockManager.callback = null;
  }
}

四、实战一:隔空手势触发上传

这是最特殊的用法------隔空手势事件不用于传输数据,而是作为应用内操作触发器。在云星图中,用户在上传页添加好图片后,隔空握拳即可一键开始批量上传。虽然使用频率不高,但交互体验非常出色。

4.1 页面生命周期中注册/注销

typescript 复制代码
// NormalUpLoad.ets
@State private isGestureUploadLocked: boolean = false;

async aboutToAppear(): Promise<void> {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  GestureShareManager.enable(context, {
    onGestureDetected: (target) => {
      this.handleGestureUpload(target);
    },
    onError: (err) => {
      hilog.error(0x0000, 'NormalUpLoad', `隔空监听错误: ${err.message}`);
    }
  });
}

async aboutToDisappear(): Promise<void> {
  GestureShareManager.disable();
}

4.2 手势回调中触发上传

手势回调内部的处理决策流程:
#mermaid-svg-w0XCBEqiQdXZBjAC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w0XCBEqiQdXZBjAC .error-icon{fill:#552222;}#mermaid-svg-w0XCBEqiQdXZBjAC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w0XCBEqiQdXZBjAC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w0XCBEqiQdXZBjAC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w0XCBEqiQdXZBjAC .marker.cross{stroke:#333333;}#mermaid-svg-w0XCBEqiQdXZBjAC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w0XCBEqiQdXZBjAC p{margin:0;}#mermaid-svg-w0XCBEqiQdXZBjAC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster-label text{fill:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster-label span{color:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster-label span p{background-color:transparent;}#mermaid-svg-w0XCBEqiQdXZBjAC .label text,#mermaid-svg-w0XCBEqiQdXZBjAC span{fill:#333;color:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC .node rect,#mermaid-svg-w0XCBEqiQdXZBjAC .node circle,#mermaid-svg-w0XCBEqiQdXZBjAC .node ellipse,#mermaid-svg-w0XCBEqiQdXZBjAC .node polygon,#mermaid-svg-w0XCBEqiQdXZBjAC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w0XCBEqiQdXZBjAC .rough-node .label text,#mermaid-svg-w0XCBEqiQdXZBjAC .node .label text,#mermaid-svg-w0XCBEqiQdXZBjAC .image-shape .label,#mermaid-svg-w0XCBEqiQdXZBjAC .icon-shape .label{text-anchor:middle;}#mermaid-svg-w0XCBEqiQdXZBjAC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w0XCBEqiQdXZBjAC .rough-node .label,#mermaid-svg-w0XCBEqiQdXZBjAC .node .label,#mermaid-svg-w0XCBEqiQdXZBjAC .image-shape .label,#mermaid-svg-w0XCBEqiQdXZBjAC .icon-shape .label{text-align:center;}#mermaid-svg-w0XCBEqiQdXZBjAC .node.clickable{cursor:pointer;}#mermaid-svg-w0XCBEqiQdXZBjAC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w0XCBEqiQdXZBjAC .arrowheadPath{fill:#333333;}#mermaid-svg-w0XCBEqiQdXZBjAC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w0XCBEqiQdXZBjAC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w0XCBEqiQdXZBjAC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w0XCBEqiQdXZBjAC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w0XCBEqiQdXZBjAC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w0XCBEqiQdXZBjAC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster text{fill:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC .cluster span{color:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w0XCBEqiQdXZBjAC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w0XCBEqiQdXZBjAC rect.text{fill:none;stroke-width:0;}#mermaid-svg-w0XCBEqiQdXZBjAC .icon-shape,#mermaid-svg-w0XCBEqiQdXZBjAC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w0XCBEqiQdXZBjAC .icon-shape p,#mermaid-svg-w0XCBEqiQdXZBjAC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w0XCBEqiQdXZBjAC .icon-shape .label rect,#mermaid-svg-w0XCBEqiQdXZBjAC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w0XCBEqiQdXZBjAC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w0XCBEqiQdXZBjAC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w0XCBEqiQdXZBjAC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



手势触发

onGestureDetected(target)
uploadList

为空?
Toast: 请先添加图片
isAllUploading

或 isGestureUploadLocked?
Toast: 上传进行中,请稍后
isGestureUploadLocked = true
Toast: 隔空上传已触发
startBatchUpload()
finally: isGestureUploadLocked = false

对应代码:

typescript 复制代码
private handleGestureUpload(target: harmonyShare.SharableTarget): void {
  const prompt = this.getUIContext().getPromptAction();

  // 前置校验:列表为空
  if (this.uploadList.length === 0) {
    prompt.showToast({ message: '请先添加图片' });
    return;
  }

  // 防重复触发:上传中或锁定期内忽略
  if (this.isAllUploading || this.isGestureUploadLocked) {
    prompt.showToast({ message: '上传进行中,请稍后' });
    return;
  }

  this.isGestureUploadLocked = true;
  prompt.showToast({ message: '隔空上传已触发' });

  // 复用已有的批量上传方法
  this.startBatchUpload().finally(() => {
    this.isGestureUploadLocked = false;
  });
}

设计要点:

  • isGestureUploadLocked 互斥锁:手势可能在短时间内连续触发(用户手抖),用独立锁保证同一时间只有一次上传在执行。
  • target 参数不使用 :此场景下不需要向对端传数据,target 仅作为事件信号。但不能不处理 target ------如果 3 秒内不调用 target.share() / target.clarifyNonShare() / target.reject(),系统会报超时。这里选择忽略(让系统自动超时),因为业务上不需要反馈。

提示 :如果你的场景需要更严谨的处理,可以在回调内立即调用 target.clarifyNonShare({ message: '触发本机上传' }),告知系统本次不传数据。

4.3 Tab 切回时重新注册

用户可能切到其他 Tab 再切回来,此时需要重新绑定回调。整个页面生命周期与监听注册的关系如下:
#mermaid-svg-q0BTl8utT71hGWkD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-q0BTl8utT71hGWkD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-q0BTl8utT71hGWkD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-q0BTl8utT71hGWkD .error-icon{fill:#552222;}#mermaid-svg-q0BTl8utT71hGWkD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-q0BTl8utT71hGWkD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-q0BTl8utT71hGWkD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-q0BTl8utT71hGWkD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-q0BTl8utT71hGWkD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-q0BTl8utT71hGWkD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-q0BTl8utT71hGWkD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-q0BTl8utT71hGWkD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-q0BTl8utT71hGWkD .marker.cross{stroke:#333333;}#mermaid-svg-q0BTl8utT71hGWkD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-q0BTl8utT71hGWkD p{margin:0;}#mermaid-svg-q0BTl8utT71hGWkD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-q0BTl8utT71hGWkD .cluster-label text{fill:#333;}#mermaid-svg-q0BTl8utT71hGWkD .cluster-label span{color:#333;}#mermaid-svg-q0BTl8utT71hGWkD .cluster-label span p{background-color:transparent;}#mermaid-svg-q0BTl8utT71hGWkD .label text,#mermaid-svg-q0BTl8utT71hGWkD span{fill:#333;color:#333;}#mermaid-svg-q0BTl8utT71hGWkD .node rect,#mermaid-svg-q0BTl8utT71hGWkD .node circle,#mermaid-svg-q0BTl8utT71hGWkD .node ellipse,#mermaid-svg-q0BTl8utT71hGWkD .node polygon,#mermaid-svg-q0BTl8utT71hGWkD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-q0BTl8utT71hGWkD .rough-node .label text,#mermaid-svg-q0BTl8utT71hGWkD .node .label text,#mermaid-svg-q0BTl8utT71hGWkD .image-shape .label,#mermaid-svg-q0BTl8utT71hGWkD .icon-shape .label{text-anchor:middle;}#mermaid-svg-q0BTl8utT71hGWkD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-q0BTl8utT71hGWkD .rough-node .label,#mermaid-svg-q0BTl8utT71hGWkD .node .label,#mermaid-svg-q0BTl8utT71hGWkD .image-shape .label,#mermaid-svg-q0BTl8utT71hGWkD .icon-shape .label{text-align:center;}#mermaid-svg-q0BTl8utT71hGWkD .node.clickable{cursor:pointer;}#mermaid-svg-q0BTl8utT71hGWkD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-q0BTl8utT71hGWkD .arrowheadPath{fill:#333333;}#mermaid-svg-q0BTl8utT71hGWkD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-q0BTl8utT71hGWkD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-q0BTl8utT71hGWkD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-q0BTl8utT71hGWkD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-q0BTl8utT71hGWkD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-q0BTl8utT71hGWkD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-q0BTl8utT71hGWkD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-q0BTl8utT71hGWkD .cluster text{fill:#333;}#mermaid-svg-q0BTl8utT71hGWkD .cluster span{color:#333;}#mermaid-svg-q0BTl8utT71hGWkD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-q0BTl8utT71hGWkD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-q0BTl8utT71hGWkD rect.text{fill:none;stroke-width:0;}#mermaid-svg-q0BTl8utT71hGWkD .icon-shape,#mermaid-svg-q0BTl8utT71hGWkD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-q0BTl8utT71hGWkD .icon-shape p,#mermaid-svg-q0BTl8utT71hGWkD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-q0BTl8utT71hGWkD .icon-shape .label rect,#mermaid-svg-q0BTl8utT71hGWkD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-q0BTl8utT71hGWkD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-q0BTl8utT71hGWkD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-q0BTl8utT71hGWkD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 监听状态
NormalUpLoad 页面生命周期
切走
切回上传页

onTabChanged
aboutToAppear
页面可见
用户切换 Tab
页面不可见

(不触发 aboutToDisappear)
重新 enable
aboutToDisappear
enable 注册手势
监听中
disable 注销

对应代码:

typescript 复制代码
@StorageLink(StateKeys.FIRST_LEVEL_INDEX) @Watch('onTabChanged')
firstLevelIndex: number = 0;

private onTabChanged(): void {
  if (this.firstLevelIndex === 1) { // 1 = 上传页
    GestureShareManager.enable(this.context, {
      onGestureDetected: (target) => {
        this.handleGestureUpload(target);
      },
      onError: (err) => {
        hilog.error(0x0000, 'NormalUpLoad', `隔空监听错误: ${err.message}`);
      }
    });
    this.reInitCloudService();
  }
}

五、实战二:隔空/碰一碰分享配置链接

在设置页配置好图床后,用户可以通过隔空手势或碰一碰将配置以 AppLinking 链接推送到对端设备,对端打开链接即可自动导入配置。

端到端流程如下:
#mermaid-svg-8g7HhTJg7dhlVNCK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8g7HhTJg7dhlVNCK .error-icon{fill:#552222;}#mermaid-svg-8g7HhTJg7dhlVNCK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8g7HhTJg7dhlVNCK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8g7HhTJg7dhlVNCK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8g7HhTJg7dhlVNCK .marker.cross{stroke:#333333;}#mermaid-svg-8g7HhTJg7dhlVNCK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8g7HhTJg7dhlVNCK p{margin:0;}#mermaid-svg-8g7HhTJg7dhlVNCK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster-label text{fill:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster-label span{color:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster-label span p{background-color:transparent;}#mermaid-svg-8g7HhTJg7dhlVNCK .label text,#mermaid-svg-8g7HhTJg7dhlVNCK span{fill:#333;color:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK .node rect,#mermaid-svg-8g7HhTJg7dhlVNCK .node circle,#mermaid-svg-8g7HhTJg7dhlVNCK .node ellipse,#mermaid-svg-8g7HhTJg7dhlVNCK .node polygon,#mermaid-svg-8g7HhTJg7dhlVNCK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8g7HhTJg7dhlVNCK .rough-node .label text,#mermaid-svg-8g7HhTJg7dhlVNCK .node .label text,#mermaid-svg-8g7HhTJg7dhlVNCK .image-shape .label,#mermaid-svg-8g7HhTJg7dhlVNCK .icon-shape .label{text-anchor:middle;}#mermaid-svg-8g7HhTJg7dhlVNCK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8g7HhTJg7dhlVNCK .rough-node .label,#mermaid-svg-8g7HhTJg7dhlVNCK .node .label,#mermaid-svg-8g7HhTJg7dhlVNCK .image-shape .label,#mermaid-svg-8g7HhTJg7dhlVNCK .icon-shape .label{text-align:center;}#mermaid-svg-8g7HhTJg7dhlVNCK .node.clickable{cursor:pointer;}#mermaid-svg-8g7HhTJg7dhlVNCK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8g7HhTJg7dhlVNCK .arrowheadPath{fill:#333333;}#mermaid-svg-8g7HhTJg7dhlVNCK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8g7HhTJg7dhlVNCK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8g7HhTJg7dhlVNCK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8g7HhTJg7dhlVNCK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8g7HhTJg7dhlVNCK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8g7HhTJg7dhlVNCK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster text{fill:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK .cluster span{color:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8g7HhTJg7dhlVNCK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8g7HhTJg7dhlVNCK rect.text{fill:none;stroke-width:0;}#mermaid-svg-8g7HhTJg7dhlVNCK .icon-shape,#mermaid-svg-8g7HhTJg7dhlVNCK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8g7HhTJg7dhlVNCK .icon-shape p,#mermaid-svg-8g7HhTJg7dhlVNCK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8g7HhTJg7dhlVNCK .icon-shape .label rect,#mermaid-svg-8g7HhTJg7dhlVNCK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8g7HhTJg7dhlVNCK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8g7HhTJg7dhlVNCK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8g7HhTJg7dhlVNCK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接收端
发送端(CloudSetting 页)
系统跨设备传输
用户配置好图床
隔空手势/碰一碰触发
configToImportData

配置序列化
encodeURIComponent

URL 编码
拼接 AppLinking URL
构造 SharedData

UTD: HYPERLINK
target.share(shareData)
系统收到链接
自动打开 AppLinking
EntryAbility.onNewWant
解析 URL config 参数
importDataToConfig

还原配置
StorageUtil.saveConfig

保存到本地
跳转设置页

表单自动填充

5.1 注册两种监听

typescript 复制代码
// CloudSetting.ets
aboutToAppear() {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  // 隔空传送
  GestureShareManager.enable(context, {
    onGestureDetected: this.handleGestureShare
  });

  // 碰一碰
  KnockManager.enable(context, {
    onKnockDetected: this.handleKnockShare
  });
}

aboutToDisappear() {
  GestureShareManager.disable();
  KnockManager.disable();
}

5.2 构造分享数据并推送

两种手势的回调逻辑完全一致------都是把当前配置编码为 AppLinking 链接,通过 target.share() 推送:

typescript 复制代码
private handleGestureShare = (target: harmonyShare.SharableTarget) => {
  const appLinkingUrl = this.generateAppLinkingUrl();
  if (!appLinkingUrl) return;

  const shareData = new systemShare.SharedData({
    utd: utd.UniformDataType.HYPERLINK,   // 超链接类型
    content: appLinkingUrl,                // AppLinking 链接
    title: `${this.getProviderName()}配置导入`,
    description: '接收后自动导入配置'
  });

  target.share(shareData).then(() => {
    promptAction.showToast({ message: `${this.getProviderName()}配置已准备就绪` });
  }).catch((err: BusinessError) => {
    promptAction.showToast({ message: `配置准备失败:${err.message}` });
  });
};

// 碰一碰回调完全相同
private handleKnockShare = (target: harmonyShare.SharableTarget) => {
  this.handleGestureShare(target); // 直接复用
};

5.3 生成 AppLinking 配置链接

typescript 复制代码
private generateAppLinkingUrl(): string | null {
  if (!this.isConfigComplete()) {
    promptAction.showToast({ message: '请先填写完整配置信息' });
    return null;
  }
  const exportData: ImportConfigData = configToImportData(this.config);
  const configJson = JSON.stringify(exportData);
  const encodedConfig = encodeURIComponent(configJson);
  return `https://你的域名/import?config=${encodedConfig}`;
}

对端设备收到链接后,系统会自动打开浏览器跳转到该 AppLinking,应用通过 EntryAbility.onNewWant 解析 URL 参数即可导入配置。

PS: 需要注意本示例代码中你的域名部分需要替换为自己的AppLink域名

六、实战三:隔空/碰一碰分享图片

在图片预览页,用户可以通过隔空手势或碰一碰将当前查看的云端图片推送到对端设备。由于图片在云端,需要先下载到本地缓存再分享。

分享图片的完整处理流程:
#mermaid-svg-2hOgXUYbCRooGzoR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2hOgXUYbCRooGzoR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2hOgXUYbCRooGzoR .error-icon{fill:#552222;}#mermaid-svg-2hOgXUYbCRooGzoR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2hOgXUYbCRooGzoR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2hOgXUYbCRooGzoR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2hOgXUYbCRooGzoR .marker.cross{stroke:#333333;}#mermaid-svg-2hOgXUYbCRooGzoR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2hOgXUYbCRooGzoR p{margin:0;}#mermaid-svg-2hOgXUYbCRooGzoR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2hOgXUYbCRooGzoR .cluster-label text{fill:#333;}#mermaid-svg-2hOgXUYbCRooGzoR .cluster-label span{color:#333;}#mermaid-svg-2hOgXUYbCRooGzoR .cluster-label span p{background-color:transparent;}#mermaid-svg-2hOgXUYbCRooGzoR .label text,#mermaid-svg-2hOgXUYbCRooGzoR span{fill:#333;color:#333;}#mermaid-svg-2hOgXUYbCRooGzoR .node rect,#mermaid-svg-2hOgXUYbCRooGzoR .node circle,#mermaid-svg-2hOgXUYbCRooGzoR .node ellipse,#mermaid-svg-2hOgXUYbCRooGzoR .node polygon,#mermaid-svg-2hOgXUYbCRooGzoR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2hOgXUYbCRooGzoR .rough-node .label text,#mermaid-svg-2hOgXUYbCRooGzoR .node .label text,#mermaid-svg-2hOgXUYbCRooGzoR .image-shape .label,#mermaid-svg-2hOgXUYbCRooGzoR .icon-shape .label{text-anchor:middle;}#mermaid-svg-2hOgXUYbCRooGzoR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2hOgXUYbCRooGzoR .rough-node .label,#mermaid-svg-2hOgXUYbCRooGzoR .node .label,#mermaid-svg-2hOgXUYbCRooGzoR .image-shape .label,#mermaid-svg-2hOgXUYbCRooGzoR .icon-shape .label{text-align:center;}#mermaid-svg-2hOgXUYbCRooGzoR .node.clickable{cursor:pointer;}#mermaid-svg-2hOgXUYbCRooGzoR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2hOgXUYbCRooGzoR .arrowheadPath{fill:#333333;}#mermaid-svg-2hOgXUYbCRooGzoR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2hOgXUYbCRooGzoR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2hOgXUYbCRooGzoR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2hOgXUYbCRooGzoR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2hOgXUYbCRooGzoR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2hOgXUYbCRooGzoR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2hOgXUYbCRooGzoR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2hOgXUYbCRooGzoR .cluster text{fill:#333;}#mermaid-svg-2hOgXUYbCRooGzoR .cluster span{color:#333;}#mermaid-svg-2hOgXUYbCRooGzoR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2hOgXUYbCRooGzoR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2hOgXUYbCRooGzoR rect.text{fill:none;stroke-width:0;}#mermaid-svg-2hOgXUYbCRooGzoR .icon-shape,#mermaid-svg-2hOgXUYbCRooGzoR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2hOgXUYbCRooGzoR .icon-shape p,#mermaid-svg-2hOgXUYbCRooGzoR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2hOgXUYbCRooGzoR .icon-shape .label rect,#mermaid-svg-2hOgXUYbCRooGzoR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2hOgXUYbCRooGzoR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2hOgXUYbCRooGzoR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2hOgXUYbCRooGzoR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否





手势/碰一碰触发

target: SharableTarget
Toast: 正在准备传输...
当前图片 URL

存在?
返回,不处理
http.createHttp

下载云端图片
下载成功?
Toast: 传输失败
fs.open + fs.write

写入缓存文件
fileUri.getUriFromPath

转换为 file:// URI
utd.getUniformDataTypeByFilenameExtension

获取细粒度 UTD 类型
new SharedData

构造分享数据
target.share(shareData)
推送成功?
Toast: 准备完成
Toast: 传输失败

6.1 注册监听(页面可见时)

typescript 复制代码
// PicView.ets
.onShown(() => {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  GestureShareManager.enable(context, {
    onGestureDetected: this.handleGestureShare,
    onError: (err) => {
      hilog.error(0x0000, 'PicView', `隔空监听错误: ${err.message}`);
    }
  });

  KnockManager.enable(context, {
    onKnockDetected: this.handleKnockShare,
    onError: (err) => {
      hilog.error(0x0000, 'PicView', `碰一碰监听错误: ${err.message}`);
    }
  });
})
.onHidden(() => {
  GestureShareManager.disable();
  KnockManager.disable();
})

6.2 下载图片并推送

typescript 复制代码
private handleGestureShare = (target: harmonyShare.SharableTarget) => {
  this.executeShare(target);
};

private handleKnockShare = (target: harmonyShare.SharableTarget) => {
  this.executeShare(target);
};

private async executeShare(target: harmonyShare.SharableTarget): Promise<void> {
  const currentImageUrl = this.imgUrls[this.currentIndex];
  if (!currentImageUrl) return;

  const uiContext = this.getUIContext();
  const promptAction = uiContext.getPromptAction();
  promptAction.showToast({ message: '检测到分享事件,正在准备传输...' });

  try {
    const context = uiContext.getHostContext() as common.UIAbilityContext;
    const cacheDir = context.cacheDir;
    const imageFileName = `share_${Date.now()}.jpg`;
    const imagePath = `${cacheDir}/${imageFileName}`;

    // 1. 下载云端图片到本地缓存
    const httpRequest = http.createHttp();
    const response = await httpRequest.request(currentImageUrl, {
      method: http.RequestMethod.GET,
      expectDataType: http.HttpDataType.ARRAY_BUFFER,
      connectTimeout: 60000,
      readTimeout: 60000
    });

    if (response.responseCode !== http.ResponseCode.OK) {
      throw new Error('图片下载失败');
    }

    // 2. 写入缓存文件
    const arrayBuffer = response.result as ArrayBuffer;
    const file = await fs.open(imagePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
    await fs.write(file.fd, arrayBuffer);
    await fs.close(file.fd);

    // 3. 构造分享数据(必须用 fileUri 转换为跨应用可访问的 URI)
    const shareUri = fileUri.getUriFromPath(imagePath);
    const utdTypeId = utd.getUniformDataTypeByFilenameExtension('.jpg', utd.UniformDataType.IMAGE);
    const shareData = new systemShare.SharedData({
      utd: utdTypeId,
      uri: shareUri,
      title: '来自云星图的图片',
      description: '碰一碰/隔空分享'
    });

    // 4. 推送到对端
    await target.share(shareData);
    promptAction.showToast({ message: '准备完成' });
  } catch (error) {
    const err = error as BusinessError;
    promptAction.showToast({ message: `传输失败:${err.message || '请重试'}` });
  }
}

关键细节:

  • 必须先下载再分享target.share() 要求传入本地文件 URI,不能直接传网络 URL。
  • fileUri.getUriFromPath :将文件系统路径转换为 file:// URI,这是跨应用/跨设备可访问的格式。
  • UTD 类型用细粒度 :用 getUniformDataTypeByFilenameExtension('.jpg', IMAGE) 而非通用 IMAGE,有助于对端系统精准匹配接收应用。

七、三种场景对比

隔空触发上传 分享配置链接 分享图片
使用事件 gesturesShare gesturesShare + knockShare gesturesShare + knockShare
target 用法 不调用(忽略超时) target.share(linkData) target.share(imageData)
UTD 类型 HYPERLINK IMAGE(细粒度 .jpg)
数据来源 本机配置序列化 云端下载到缓存
注册时机 aboutToAppear + Tab 切回 aboutToAppear onShown(页面可见)
注销时机 aboutToDisappear aboutToDisappear onHidden(页面隐藏)
防重机制 isGestureUploadLocked 互斥锁 无需(单次推送) 无需(单次推送)

三种场景在 target 处理上的差异对比:
#mermaid-svg-aCTkC8WvdXlhjTtH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aCTkC8WvdXlhjTtH .error-icon{fill:#552222;}#mermaid-svg-aCTkC8WvdXlhjTtH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aCTkC8WvdXlhjTtH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aCTkC8WvdXlhjTtH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aCTkC8WvdXlhjTtH .marker.cross{stroke:#333333;}#mermaid-svg-aCTkC8WvdXlhjTtH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aCTkC8WvdXlhjTtH p{margin:0;}#mermaid-svg-aCTkC8WvdXlhjTtH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster-label text{fill:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster-label span{color:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster-label span p{background-color:transparent;}#mermaid-svg-aCTkC8WvdXlhjTtH .label text,#mermaid-svg-aCTkC8WvdXlhjTtH span{fill:#333;color:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH .node rect,#mermaid-svg-aCTkC8WvdXlhjTtH .node circle,#mermaid-svg-aCTkC8WvdXlhjTtH .node ellipse,#mermaid-svg-aCTkC8WvdXlhjTtH .node polygon,#mermaid-svg-aCTkC8WvdXlhjTtH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aCTkC8WvdXlhjTtH .rough-node .label text,#mermaid-svg-aCTkC8WvdXlhjTtH .node .label text,#mermaid-svg-aCTkC8WvdXlhjTtH .image-shape .label,#mermaid-svg-aCTkC8WvdXlhjTtH .icon-shape .label{text-anchor:middle;}#mermaid-svg-aCTkC8WvdXlhjTtH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aCTkC8WvdXlhjTtH .rough-node .label,#mermaid-svg-aCTkC8WvdXlhjTtH .node .label,#mermaid-svg-aCTkC8WvdXlhjTtH .image-shape .label,#mermaid-svg-aCTkC8WvdXlhjTtH .icon-shape .label{text-align:center;}#mermaid-svg-aCTkC8WvdXlhjTtH .node.clickable{cursor:pointer;}#mermaid-svg-aCTkC8WvdXlhjTtH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aCTkC8WvdXlhjTtH .arrowheadPath{fill:#333333;}#mermaid-svg-aCTkC8WvdXlhjTtH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aCTkC8WvdXlhjTtH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aCTkC8WvdXlhjTtH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aCTkC8WvdXlhjTtH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aCTkC8WvdXlhjTtH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aCTkC8WvdXlhjTtH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster text{fill:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH .cluster span{color:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aCTkC8WvdXlhjTtH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aCTkC8WvdXlhjTtH rect.text{fill:none;stroke-width:0;}#mermaid-svg-aCTkC8WvdXlhjTtH .icon-shape,#mermaid-svg-aCTkC8WvdXlhjTtH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aCTkC8WvdXlhjTtH .icon-shape p,#mermaid-svg-aCTkC8WvdXlhjTtH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aCTkC8WvdXlhjTtH .icon-shape .label rect,#mermaid-svg-aCTkC8WvdXlhjTtH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aCTkC8WvdXlhjTtH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aCTkC8WvdXlhjTtH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aCTkC8WvdXlhjTtH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 手势/碰一碰触发

获得 target: SharableTarget
场景一:隔空触发上传
不调用 target 方法

(让系统自动超时)
执行本机批量上传
场景二:分享配置链接
配置序列化为 AppLinking
target.share(SharedData)

UTD: HYPERLINK
对端打开链接自动导入
场景三:分享图片
下载云端图片到缓存
构造 SharedData

UTD: IMAGE
target.share(SharedData)
对端接收图片文件

八、常见问题与最佳实践

8.1 常见问题

问题 原因 解决
回调不触发 设备不支持 / 未开启系统开关 canIUse 检测 + 引导用户到「设置 -> 快捷启动和手势 -> 隔空传送」
off() 注销无效 传入的 windowId 与 on() 时不一致 管理器中保存 windowId,off 时用同一值
分享超时失败 3 秒内未调用 target.share() 网络下载图片耗时较长时,先展示 Toast 再异步下载
Tab 切回后手势失效 页面未重新注册监听 监听 FIRST_LEVEL_INDEX 变化,切回时重新 enable
两台设备无法发现 未登录同一华为账号 / 未加信任设备 引导用户在「设置 -> 超级终端」中添加信任设备

8.2 页面生命周期配对速查

不同页面的注册/注销时机不同,需严格配对:
#mermaid-svg-ck90kFnd2VmXG6ZI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ck90kFnd2VmXG6ZI .error-icon{fill:#552222;}#mermaid-svg-ck90kFnd2VmXG6ZI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ck90kFnd2VmXG6ZI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ck90kFnd2VmXG6ZI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ck90kFnd2VmXG6ZI .marker.cross{stroke:#333333;}#mermaid-svg-ck90kFnd2VmXG6ZI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ck90kFnd2VmXG6ZI p{margin:0;}#mermaid-svg-ck90kFnd2VmXG6ZI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster-label text{fill:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster-label span{color:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster-label span p{background-color:transparent;}#mermaid-svg-ck90kFnd2VmXG6ZI .label text,#mermaid-svg-ck90kFnd2VmXG6ZI span{fill:#333;color:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI .node rect,#mermaid-svg-ck90kFnd2VmXG6ZI .node circle,#mermaid-svg-ck90kFnd2VmXG6ZI .node ellipse,#mermaid-svg-ck90kFnd2VmXG6ZI .node polygon,#mermaid-svg-ck90kFnd2VmXG6ZI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ck90kFnd2VmXG6ZI .rough-node .label text,#mermaid-svg-ck90kFnd2VmXG6ZI .node .label text,#mermaid-svg-ck90kFnd2VmXG6ZI .image-shape .label,#mermaid-svg-ck90kFnd2VmXG6ZI .icon-shape .label{text-anchor:middle;}#mermaid-svg-ck90kFnd2VmXG6ZI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ck90kFnd2VmXG6ZI .rough-node .label,#mermaid-svg-ck90kFnd2VmXG6ZI .node .label,#mermaid-svg-ck90kFnd2VmXG6ZI .image-shape .label,#mermaid-svg-ck90kFnd2VmXG6ZI .icon-shape .label{text-align:center;}#mermaid-svg-ck90kFnd2VmXG6ZI .node.clickable{cursor:pointer;}#mermaid-svg-ck90kFnd2VmXG6ZI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ck90kFnd2VmXG6ZI .arrowheadPath{fill:#333333;}#mermaid-svg-ck90kFnd2VmXG6ZI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ck90kFnd2VmXG6ZI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ck90kFnd2VmXG6ZI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ck90kFnd2VmXG6ZI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ck90kFnd2VmXG6ZI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ck90kFnd2VmXG6ZI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster text{fill:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI .cluster span{color:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ck90kFnd2VmXG6ZI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ck90kFnd2VmXG6ZI rect.text{fill:none;stroke-width:0;}#mermaid-svg-ck90kFnd2VmXG6ZI .icon-shape,#mermaid-svg-ck90kFnd2VmXG6ZI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ck90kFnd2VmXG6ZI .icon-shape p,#mermaid-svg-ck90kFnd2VmXG6ZI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ck90kFnd2VmXG6ZI .icon-shape .label rect,#mermaid-svg-ck90kFnd2VmXG6ZI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ck90kFnd2VmXG6ZI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ck90kFnd2VmXG6ZI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ck90kFnd2VmXG6ZI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PicView(NavDestination)
onShown

enable x2
页面可见
onHidden

disable x2
CloudSetting(Tab 页)
aboutToAppear

enable x2
页面可见
aboutToDisappear

disable x2
NormalUpLoad(Tab 页)
切回
切走
aboutToAppear

enable
页面可见
@Watch Tab 切换
重新 enable
aboutToDisappear

disable

8.3 最佳实践

  1. 统一封装管理器 :将 on/off 逻辑收敛到 GestureShareManager / KnockManager,页面只关心回调业务,不直接接触底层 API。

  2. 生命周期严格配对 :每个 enable 必须有对应的 disable。推荐在 aboutToAppear/aboutToDisappearonShown/onHidden 中配对调用,避免页面销毁后回调仍被触发导致空指针。

  3. Tab 切换重新注册 :ArkUI 的 Tab 页面切换不会触发 aboutToDisappear,需要用 @Watch 监听 Tab 索引变化,在切回时重新 enable 以刷新回调。

  4. 3 秒超时处理 :如果回调内需要异步准备数据(如下载图片),建议先同步展示提示,异步完成后调用 target.share()。超过 3 秒系统会自动判定超时。

  5. 能力降级canIUse 返回 false 时,应隐藏相关功能入口或提供系统分享面板作为替代方案。

  6. UTD 类型选择

    分享内容 推荐 UTD
    纯文本 PLAIN_TEXT
    URL / AppLinking HYPERLINK
    图片文件 getUniformDataTypeByFilenameExtension('.jpg', IMAGE)
    视频文件 getUniformDataTypeByFilenameExtension('.mp4', VIDEO)

九、总结

跨设备分享是 HarmonyOS 分布式能力的典型应用,本教程介绍了三种实战场景:

  • 隔空手势触发上传:将手势事件作为本机操作触发器,不传数据,适合"挥手即执行"的交互
  • 分享配置链接 :将配置序列化为 AppLinking,通过 target.share() 推送超链接,对端自动导入
  • 分享图片 :下载云端图片到缓存,构造 SharedData 推送文件 URI

开发建议:

  1. 先用 canIUse 检测设备能力,不支持时优雅降级
  2. 封装统一管理器,页面只关心业务回调
  3. 生命周期严格配对 enable/disable,Tab 切换时用 @Watch 重新注册
  4. 注意 3 秒超时限制,异步准备数据时先展示提示
  5. 文件分享必须先转为本地 file:// URI,不能直接传网络地址
相关推荐
用户0934077735141 小时前
HarmonyOS WPS Open SDK 二开周回顾:从注册到关窗回传怎么串
typescript·harmonyos
程序员黑豆2 小时前
鸿蒙应用开发实战:轻松实现列表上拉加载更多
前端·华为·harmonyos
独隅3 小时前
DevEco Code Plan+Build 双 Agent 协同开发实效解析
ide·华为
fiona20263 小时前
HarmonyOS应用《玄象》开发实战:LunarCalendar.ets 农历计算核心:朔望月 + 节气 + 闰月推算
harmonyos·鸿蒙
yaoyaoxingzhe3 小时前
HarmonyOS应用开发实战:猫猫大作战-`$r` 与 `$rawfile` 的区别、资源目录结构、多分辨率适配
harmonyos·鸿蒙
youtootech15 小时前
HarmonyOS 实战教程(八):个人中心与华为云服务集成 —— 以「柚兔自测量表」为例
华为·华为云·harmonyos
红烧大青虫16 小时前
HarmonyOS应用《玄象》开发实战:掷钱动画:animateTo + 缓动曲线的物理感模拟
harmonyos·鸿蒙
二流小码农17 小时前
鸿蒙开发:实现文本渐变效果
android·ios·harmonyos
程序员黑豆18 小时前
鸿蒙应用开发:Refresh + List 下拉刷新组件使用教程
前端·华为·harmonyos