【HarmonyOS 7新能力|031】碰一碰精准分享工程封装:把接入逻辑放进可维护的分层结构
【HarmonyOS 7新能力|031】碰一碰精准分享工程封装:把接入逻辑放进可维护的分层结构

跨设备分享如果只做到“附近有设备就发送”,用户仍可能把文件送错窗口。多窗口、旋转、缩放和布局更新都会改变触点对应的目标;坐标命中正确,也不代表该窗口具备接收当前内容的能力。
本文把精准分享拆成触发交互、会话、目标解析、能力校验、传输和治理六层,构建“识别设备—解析窗口—变换坐标—消除歧义—确认内容—受控传输”的链路。文中类型和算法是应用侧教学封装,不代表 HarmonyOS 7 官方 API;设备发现、窗口信息与坐标能力应以当前 SDK 和华为官方文档为准。
一、先确定什么叫精准
精准不是触点坐标落在某个矩形里,而是同时满足设备可信、会话有效、窗口可见可交互、内容类型受支持且用户明确确认。任何条件缺失都不能自动发送。
第一版只分享单个本地文件,目标限定为前台可接收窗口。验收包括:窗口重排后旧坐标失效;多个重叠窗口要求选择;敏感内容提示确认;取消后停止;重复事件不重复发送;传输完成后校验完整性。
二、用会话绑定一次碰一碰
事件、设备和坐标必须属于同一短生命周期会话,避免把上一次触点用到新布局上。
interface ShareSession {
id: string
sourceDeviceId: string
targetDeviceId: string
revision: number
createdAt: number
expiresAt: number
state: 'detecting' | 'resolving' | 'confirming' | 'transferring' | 'done' | 'cancelled' | 'failed'
}
会话过期、设备变化或用户取消后都进入终态。迟到回调只能清理资源。
三、坐标携带来源空间
一个 x, y 没有意义,必须知道它属于物理屏幕、逻辑屏幕、窗口还是内容区域,并记录采样时的布局版本。
type CoordinateSpace = 'source-screen' | 'target-screen' | 'window' | 'content'
interface PointSample {
x: number
y: number
space: CoordinateSpace
layoutRevision: number
occurredAt: number
}
坐标必须是有限数值,并在对应空间边界内。布局 revision 不匹配时重新解析,不能继续命中。
四、窗口快照必须不可变
目标屏幕的窗口列表应作为一次解析的不可变快照,包含层级、可见区域、变换和接收能力摘要。
interface WindowSnapshot {
id: string
zIndex: number
visible: boolean
interactive: boolean
bounds: { left: number; top: number; right: number; bottom: number }
acceptedTypes: ReadonlyArray<string>
revision: number
}
解析完成后若系统窗口布局变化,快照整体失效,避免在新旧窗口数据之间拼接结果。
五、坐标变换保留完整链路

跨设备可能涉及缩放、平移、旋转和安全区域偏移。将变换参数集中到适配层,业务层只消费目标逻辑坐标。
interface AffineTransform {
a: number; b: number; c: number
d: number; tx: number; ty: number
}
function transform(p: PointSample, m: AffineTransform): { x: number; y: number } {
return { x: m.a * p.x + m.c * p.y + m.tx, y: m.b * p.x + m.d * p.y + m.ty }
}
变换矩阵必须来自可信的平台布局信息,不能让发送方任意声明目标坐标。
六、候选窗口按规则消歧
命中测试先过滤不可见、不可交互和不支持内容类型的窗口,再按层级排序。若仍存在多个有效候选,不应静默选择。
function hitCandidates(point: { x: number; y: number }, windows: ReadonlyArray<WindowSnapshot>): WindowSnapshot[] {
return windows.filter((w) => w.visible && w.interactive &&
point.x >= w.bounds.left && point.x <= w.bounds.right &&
point.y >= w.bounds.top && point.y <= w.bounds.bottom)
.sort((a, b) => b.zIndex - a.zIndex)
}
遮挡、透明区域和不规则窗口还需要使用平台提供的真实可交互区域,而非简单外接矩形。
七、分层架构隔离两端差异

触发层选择内容并接收事件;会话层验证设备与时效;目标解析层处理布局、候选和坐标;能力层校验窗口状态和类型;传输层管理进度与完整性;治理层处理确认、敏感内容与审计。
页面不直接调用传输能力,目标解析器也不读取文件正文。各层只交换完成任务所需的最小结构。
八、内容先生成最小摘要
确认界面只需要名称、类型、大小和敏感级别,不应为展示摘要而提前上传完整内容。
interface ShareContentSummary {
itemId: string
displayName: string
mimeType: string
bytes: number
sensitivity: 'normal' | 'personal' | 'restricted'
digest: string
}
摘要中的 digest 同时用于传输完成后的完整性比对。敏感内容提高确认等级或按策略阻断。
九、用户确认绑定目标与内容
确认不能只是一个通用“继续”按钮。界面应明确显示接收设备、目标应用或窗口、内容摘要和可能的风险。确认结果绑定会话 revision。
interface ShareConsent {
sessionId: string
revision: number
targetWindowId: string
contentDigest: string
confirmedAt: number
}
function consentMatches(c: ShareConsent, session: ShareSession, windowId: string, digest: string): boolean {
return c.sessionId === session.id && c.revision === session.revision &&
c.targetWindowId === windowId && c.contentDigest === digest
}
确认后若目标或内容发生变化,必须重新确认。
十、传输状态必须幂等可恢复
碰一碰事件可能重复到达,网络或近场链路也可能重试。同一会话和内容摘要只能创建一个传输任务。
interface TransferJob {
idempotencyKey: string
sentBytes: number
totalBytes: number
state: 'queued' | 'sending' | 'verifying' | 'completed' | 'failed' | 'cancelled'
}
function transferKey(sessionId: string, digest: string): string {
return `${sessionId}:${digest}`
}
断点恢复前重新确认目标会话仍可信,不能把旧任务自动续传到新设备。
十一、结果以完整性验证为准
发送接口返回成功,只能说明数据提交完成。接收端应核对长度与摘要,并返回结构化结果。
interface TransferReceipt {
sessionId: string
receivedBytes: number
digest: string
acceptedByWindowId: string
status: 'verified' | 'mismatch' | 'rejected'
}
只有 receipt 与原内容、目标窗口和会话都匹配,页面才显示分享完成。
十二、用变化和异常完成验收
测试覆盖:设备身份失败、会话过期、目标屏幕旋转、窗口拖动、重叠窗口、窗口关闭、坐标越界、内容类型不支持、确认后内容变化、事件重复、传输中取消、摘要不匹配和接收端拒绝。
日志记录会话号、匿名设备键、窗口类型、结果码和耗时,不记录文件正文或精确触点轨迹。完成多窗口与布局变化测试后,才能证明“精准”不是一次静态截图中的偶然命中。
碰一碰精准分享的核心不是更快发送,而是把设备、目标窗口、坐标、内容与用户确认绑定在同一受控会话中。通过快照、变换、消歧、幂等与完整性验证,跨设备分享才能兼顾准确、安全和可维护性。
更多推荐




所有评论(0)