手机里选好一张展品卡,在平板展板的某个区域碰一下,卡片就落到那里。这个流程包含两种不同结果:文件已经传到接收端,以及文件被放进用户指定的位置。前者成功而后者错位,用户仍会认为这次分享失败。
接收端需要同时处理内容、坐标和事务状态。方案以单张展品卡为例,使用系统精准分享能力传递资源与实际触碰位置,应用负责把系统坐标转换为展板位置,并保证同一分享只提交一次。
坐标的参考系必须来自接口合同
触碰坐标究竟相对屏幕、窗口还是内容区,单位是物理像素还是逻辑尺寸,需要从真实系统事件定义确认。状态栏、窗口边框、旋转和缩放都可能造成偏差,不能看到 x/y 字段就直接传给卡片布局。
将变换拆成屏幕到窗口、窗口到画布两步更容易排查。画布还有平移缩放时,使用逆变换得到内容坐标。以下只描述应用的几何计算,不代表系统一定返回屏幕像素:
interfacePoint{x:number;y:number;}interfaceCanvasTransform{originX:number;originY:number;scale:number;}functiontoCanvas(p:Point,t:CanvasTransform):Point{if(!Number.isFinite(t.scale)||t.scale<=0){thrownewError('Invalid canvas scale');}return{x:(p.x-t.originX)/t.scale,y:(p.y-t.originY)/t.scale};}所有输入点和偏移还应检查有限性。系统事件进入平台适配层时就附上参考系标识,避免后续页面猜测来源。
落点选择与文件传输分开推进
接收文件可能比坐标事件晚。接收端可以先显示待接收位置,但不要立即把正式卡片写入目录。资源可读、格式检查通过之后,再完成落点提交;失败则撤销占位并显示原因。
应用记录应包括 transferId、资源临时路径、目标展板、内容坐标和状态。若坐标越出画布,允许用户重新选点或放入待整理区,不要静默把内容丢到某个默认角落。
多窗口情况下,目标展板也要稳定标识。传输期间用户切换到另一个展板,最终卡片仍属于原请求,或进入明确的重新确认流程,不能依赖“当前活动页面”做提交。
用一次提交处理重复回调
弱连接和跨设备流程可能发生重试。应用用稳定 transferId 保存提交结果,再次收到相同传输时返回已有结果。文件哈希可用于内容校验,但不能代替业务传输 ID:用户可能合法地把同一张图放进两个不同位置。
收到请求 → 等待资源 → 检查格式与大小 → 确定落点 → 提交展板记录 → 记录 transferId → 返回结果资源复制与数据库更新需要设计失败补偿。记录写入失败时清除本次临时复制;记录已经成功但回执丢失时保留结果,让重试查到它。避免把“没有收到成功提示”当作“应该再插入一张”。
临时 URI 的寿命不要穿透到长期收藏
跨设备传来的 URI 可能只在授权范围和生命周期内有效。需要长期展示的卡片,应在有权读取时复制到自己的资源目录,并验证完整性,再保存应用自己的引用。
图片还需检查尺寸与解码成本,不能只限制压缩文件字节数。极高分辨率小文件也可能带来较大解码内存。拒绝时说明是格式、大小还是解码失败,不把所有错误都归结为“碰一碰失效”。
用网格图验收精度
第一版 Demo 在接收端显示编号网格,手机发送一张带明显边界的测试图。分别在左上、中央、右下触碰,记录系统原始坐标、变换后坐标与最终槽位。窗口移动、画布缩放、横竖屏切换后重复实验。
双端录屏应能对应同一 transferId。还要验证取消、断连和重复接收,不以单次中间位置成功代表全部坐标都正确。若某类设备不提供精准位置,则提供普通接收后手动放置路径,并明确区分两种操作。
用一个算得出的落点定位偏移错误
假设平台适配后得到窗口逻辑坐标 (260,180),画布原点为 (20,40),缩放为 2,则内容坐标应为 (120,70)。这是人工构造的数学用例:如果实际界面落在 (130,90),说明遗漏了原点扣除,不能通过挪动卡片边距修补。
| 收件方式 | 使用条件 | 交互代价 |
|---|---|---|
| 精准坐标直接落位 | 来源坐标系已确认,资源有效 | 需处理变换和越界 |
| 接收后手动选位 | 设备不支持位置或坐标无效 | 多一次操作,但位置可确认 |
先完成变换与越界测试,再加入资源提交及重复回执,最后用成对设备核对三个网格点。提交记录和 transferId 应在同一事务中生效;仅按流程先后分别写两条记录,仍可能在中断时产生重复卡片。
参考:精准分享官方解读、HarmonyOS 7 能力入口。
监听注册、坐标命中与临时分享目标
坐标必须和渲染区域使用同一单位。图鉴卡片在布局变化时重新记录全局区域,把 ArkUI 的 vp 转为 px;负坐标、空区域和重叠区域没有唯一命中。真实触碰只建立候选分享目标,用户点击分享按钮后才调用系统分享。
触碰回调注册、同一函数解除监听和一次性使用目标对象已接入。离页及切换主题时解除监听,页面生命周期编号拦截迟到回调。
exportclassKnockProbe{privateactive:boolean=false;privateregions:HitRect[]=[];setRegions(regions:HitRect[]):void{this.regions=regions.slice();}privatepending:harmonyShare.SharableTarget|undefined=undefined;privatecallback:(target:harmonyShare.SharableTarget)=>void=()=>{};start(report:(message:string)=>void):void{if(this.active){return;}this.callback=(target:harmonyShare.SharableTarget):void=>{if(!this.active){return;}this.pending=target;constcoordinate:harmonyShare.CoordinateInfo|undefined=target.getInfo().coordinate;consthit:string|undefined=coordinate?TouchSelection.pick(coordinate.screenX,coordinate.screenY,this.regions):undefined;report('命中区域:'+(hit||'无唯一命中,请手动选择')+'; '+(coordinate?'真实触碰坐标 px: '+coordinate.screenX+', '+coordinate.screenY:'收到触碰,系统未提供坐标'));};this.active=true;try{harmonyShare.on('knockShare',this.callback);}catch(error){this.active=false;throwerrorasError;}}asyncshareSelected():Promise<void>{consttarget:harmonyShare.SharableTarget|undefined=this.pending;if(!target){thrownewError('先等待真实触碰事件');}this.pending=undefined;awaittarget.share(newsystemShare.SharedData({utd:'general.plain-text',content:'玄龟 · 山海图鉴实验',title:'玄龟'}));}stop():void{if(!this.active){return;}harmonyShare.off('knockShare',this.callback);this.active=false;this.pending=undefined;}}辅助校验与边界处理使用以下实现:
exportinterfaceHitRect{id:string;left:number;top:number;width:number;height:number;}exportclassTouchSelection{staticpick(x:number,y:number,regions:HitRect[]):string|undefined{if(!Number.isFinite(x)||!Number.isFinite(y)||x<0||y<0){returnundefined;}consthits:HitRect[]=regions.filter((rect:HitRect)=>{return[rect.left,rect.top,rect.width,rect.height].every((n:number)=>Number.isFinite(n))&&rect.width>0&&rect.height>0&&x>=rect.left&&y>=rect.top&&x<rect.left+rect.width&&y<rect.top+rect.height;});// Ambiguous/overlapping regions require explicit user selection.returnhits.length===1?hits[0].id:undefined;}}用坐标和重复回调核对源码
真机触碰坐标与布局单位对应、接收设备实际收到文本。
| 检查层 | 当前处理 | 不能替代的结果 |
|---|---|---|
| 编译 | API 26 工程进行类型检查与打包 | 目标设备支持 |
| 逻辑 | 校验输入、状态或资源释放边界 | 平台服务真实返回 |
| 联调 | 按上面的输入和条件逐步执行 | 不能以按钮文案代替核心结果 |
API 26 验证边界
2026-09-23 已在 API 26 模拟器安装并启动本工程,基础实验首页可见;进入包含精准分享适配器的设备专项页后,进程被系统终止,当前日志没有给出可归因的 Kit 缺失栈。随后为本工程配置包名匹配的调试签名,签名 HAP 已安装到 HBN-AL80 真机,DeviceLabPage可见;“监听真实碰一碰”返回“已注册监听,等待真实触碰”,“解除监听”返回“监听已解除”。这确认了本应用在该真机上的监听注册与释放链路。
现有记录没有knockShare的真实回调、系统坐标或跨设备接收文本:模拟器不能替代第二台物理碰一碰设备。因此模拟器运行、监听成功和宿主测试都不能作为物理碰一碰传输成功的依据。
2026-09-23 补充把同一签名 HAP 部署到 API 26 模拟器和 HBN-AL80,两个目标均能启动基础实验首页;模拟器打开设备专项页后仍被系统终止,因而没有把它写成专项页或系统分享成功。宿主测试直接加载实际KnockProbe与TouchSelection源码,以平台 Kit 测试替身验证:负坐标、右边界与重叠区域不命中;重复start只注册一次;stop使用同一回调;分享目标仅能消费一次。这构成同包双端部署和应用逻辑联调,不构成物理碰一碰传输。完整设备、构建与测试记录见 完整设备、构建与测试记录。后续物理验收仍需具备系统能力的真实受端,再记录同一传输的原始坐标、命中区域和接收内容。
碰一碰事件、坐标与分享接口的版本约束,以 HarmonyOS 官方文档中心和当前 SDK 声明为准。
触碰边界与重复监听
矩形右边界采用半开区间,重叠命中不选择任何条目。测试也检查监听只注册一次、注销使用同一回调,以及分享目标只消费一次。宿主测试直接载入 KnockProbe 与 TouchSelection,仅以替身代替系统分享 Kit;load 与 test 属于测试框架。
test('touch: negative, right boundary and overlap are not unique hits',()=>{consta={id:'a',left:10,top:10,width:20,height:20};assert.equal(TouchSelection.pick(10,10,[a]),'a');assert.equal(TouchSelection.pick(30,20,[a]),undefined);assert.equal(TouchSelection.pick(-1,10,[a]),undefined);assert.equal(TouchSelection.pick(20,20,[a,{...a,id:'b'}]),undefined);});test('touch: register once and release exact callback; do not invent events',async()=>{letcb,on=0,off=0,shared=0;constkit={on(e,c){on++;cb=c;},off(e,c){assert.equal(c,cb);off++;}};constC=load('services/KnockProbe',{'@kit.ShareKit':{harmonyShare:kit,systemShare:{SharedData:class{constructor(record){this.record=record;}}}}}).KnockProbe;constx=newC();awaitassert.rejects(x.shareSelected());x.start(()=>{});x.start(()=>{});assert.equal(on,1);cb({getInfo:()=>({coordinate:{screenX:-1,screenY:-1}}),share:async()=>shared++});awaitx.shareSelected();awaitassert.rejects(x.shareSelected());x.stop();x.stop();assert.equal(off,1);assert.equal(shared,1);});| 操作或边界 | 应检查的结果 |
|---|---|
| 输入不满足前提 | 不调用后续平台操作 |
| 平台拒绝或抛错 | 保留错误,不生成成功状态 |
| 再次进入或重试 | 检查旧状态不会污染新任务 |