尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Harmony os 技术实战|拼豆制图36:一次导出如何挡住重复点击与并发相册弹窗

Harmony os 技术实战|拼豆制图36:一次导出如何挡住重复点击与并发相册弹窗 导出高清图要经历像素缓冲区绘制、PixelMap 创建、PNG 打包、缓存文件写入和系统相册确认。用户连续点击两次若没有保护就可能同时分配两份大缓冲区、弹出两个相册对话框、写入两条最近记录失败信息还会互相覆盖。拼豆制图用一个isExporting状态同时控制按钮禁用、按钮文案和方法入口并在finally中释放锁。本篇分析这条最小互斥链为什么有效、它还缺哪些异常语义以及当导出对象、进度或取消能力继续增长时如何升级为显式状态机。一、重复点击会同时放大四种成本70×70 导出图按 22px 格子绘制画布宽度超过 1600px高度也超过 1800px单份 RGBA 缓冲区约十余 MiB。两次任务并发不是简单多保存一张它会同时增加内存两份 BitmapCanvas 两份 PixelMap/打包过程 文件两个带时间戳的缓存 PNG 交互两个系统相册确认对话框 状态两次记录写入、两条结果文案竞争因为两个按钮“下载图纸”和“导出高清图”当前都调用同一个exportPatternToGallery()用户还可能交替点击两个入口。保护必须放在共享方法而不只是某一个按钮。二、UI 层先减少重复操作机会两个按钮都依赖同一个状态Button(this.isExporting?生成中...:下载图纸).enabled(!this.isExporting).onClick(()this.exportPatternToGallery(pattern))Button(this.isExporting?生成中...:导出高清图).enabled(!this.isExporting).onClick(()this.exportPatternToGallery(pattern))状态变为true后用户能立刻看到文案变化两枚按钮也同时禁用。这是交互反馈不是完整并发保证状态更新到下一帧前、其他调用路径或未来新增快捷入口仍可能再次调用方法。因此按钮禁用只能作为第一层业务入口还必须自守。三、方法入口的 guard 才是互斥核心导出方法开头privateasyncexportPatternToGallery(pattern:Pattern):Promisevoid{if(this.isExporting){return;}this.isExportingtrue;this.exportStatus正在生成高清编号图请稍等...;// 后续异步任务}JavaScript/ArkTS 同一线程上的这段同步前置逻辑不会在if与赋值之间插入另一个 await。第一次调用在遇到异步操作前已经把锁设为true紧随其后的第二次调用会直接返回。关键规则是锁赋值必须出现在第一个await之前。若先做异步准备再设锁两次调用都可能通过入口判断。四、finally 保证成功、失败和取消都能解锁完整结构使用try/catch/finallythis.isExportingtrue;try{this.savePatternRecord(pattern);constcontextgetContext(this)ascommon.UIAbilityContext;awaitPatternExportService.savePatternToGallery(context,pattern);this.exportStatus已保存高清编号图到系统相册并同步加入收藏和最近生成记录。;}catch(error){// 取消或失败文案}finally{this.isExportingfalse;}如果把解锁只写在成功分支PNG 打包失败、文件写入异常、用户取消相册对话框都会让按钮永久停在“生成中…”。finally的职责不是清理某一种错误而是维护互斥不变量方法离开时锁一定恢复。资源释放也采用同一思路。PixelMap、ImagePacker 和文件句柄各自在服务层的finally中释放页面锁与底层资源清理形成两级防线。五、取消不是普通失败但都要释放锁当前代码通过错误信息包含cancel、Cancel或“取消”来区分constmessageerr.messageundefined?:err.message;if(message.indexOf(cancel)0||message.indexOf(Cancel)0||message.indexOf(取消)0){this.exportStatus已取消保存到系统相册。;}else{this.exportStatus保存到系统相册失败${message};}这种字符串判断能覆盖一部分系统返回但容易受语言和版本变化影响。更稳妥的方式是让服务层返回结构化结果typeExportResult|{kind:saved;fileName:string}|{kind:cancelled}|{kind:failed;message:string};页面只根据kind更新文案。无论哪一种外层finally都执行互斥语义不受错误分类方式影响。六、记录写入时机决定“取消后是否算作品”当前savePatternRecord(pattern)在相册流程之前执行。结果是用户在系统对话框中取消生成图仍会加入最近记录和收藏。成功文案才说“同步加入收藏和最近生成记录”但实际写入早已发生。这不一定错误却必须是产品明确选择。存在两种契约契约 A点击导出即保留作品相册取消不回滚应用内记录 契约 B只有相册保存成功才保留记录若采用 B应移动调用awaitPatternExportService.savePatternToGallery(context,pattern);this.savePatternRecord(pattern);若采用 A取消文案应补充“图纸仍已保存在最近生成”避免用户误以为全部操作被撤销。并发锁能保证只有一次写入但不能替产品决定事务边界。七、锁应覆盖任务不能覆盖系统对话框之后的无关工作当前await showAssetsCreationDialog()完成后服务还会删除缓存文件再返回页面。锁覆盖整个过程是合理的因为临时文件在系统确认前不能提前删除。任务阶段可表示为enumExportPhase{Idle,Rendering,WritingCache,AwaitingGallery,Cleaning,Succeeded,Cancelled,Failed}当需求只需要防重入一个 boolean 足够当页面要显示百分比、区分“正在绘制”和“等待相册确认”、提供重试时显式阶段比多个 boolean 更安全。避免同时维护isExporting、isPacking、isSaving、isDialogOpen四个可独立变化的状态它们很容易组成不可能的组合。单一phase可派生按钮是否启用和显示文案。八、是否应该排队而不是丢弃第二次点击当前 guard 对第二次调用直接return。对同一张图的重复点击这是最符合用户预期的策略排队会在第一次完成后再次弹相册对话框反而像故障。但若将来支持批量导出不同图案策略可能改为队列interfaceExportJob{patternId:string;requestedAt:number;}队列需要去重、取消、失败继续和任务进度不能用一个 boolean 假装已经支持。当前单图交互应保持“忙时忽略”并可用轻提示告诉用户任务正在进行。还要决定用户在导出期间切换图案时怎么办。方法参数已经捕获点击时的pattern对象底层应继续导出该对象而不是中途再次调用getSelectedPattern()读取新选择。九、最小并发回归用例把底层服务替换为可控制的 Promise可验证锁行为letcalls0;constpendingnewPromisevoid((){});asyncfunctionfakeSave():Promisevoid{calls;awaitpending;}// 连续调用两次后 calls 应为 1isExporting 应为 true还应覆盖成功返回后isExportingfalse状态为成功。服务抛错后锁恢复可以再次点击。取消分支后锁恢复文案不是普通失败。两个按钮交替点击底层仍只调用一次。页面切换期间任务完成不向已销毁对象写入无效 UI 状态。最后一项需要结合 Ability 生命周期实测。若页面可能被销毁应把任务放进更稳定的服务或在更新状态前确认页面仍有效。十、设备侧验收不能只看相册里有一张图操作步骤建议快速连点同一按钮 5 次 两个按钮交替快速点击 在相册确认框中取消 再次点击并确认保存 导出过程中切换横竖屏或退到后台再回来期望结果是每轮最多出现一个系统对话框取消后按钮可再次使用确认后相册只新增一张页面状态不被旧任务覆盖。还应观察内存峰值确认没有两份导出任务并发存在。若按钮已灰但仍出现两次对话框说明还有绕过页面入口的调用若失败后一直灰检查finally是否被覆盖或状态更新发生在无效页面若相册只有一张但最近记录重复问题在记录去重而不是导出锁。十一、结语可靠的防重复导出需要三层协作按钮禁用提供即时反馈方法入口 guard 建立真正互斥finally保证所有退出路径恢复。拼豆制图的isExporting已构成这条最小闭环也揭示出下一步需要明确的产品语义——取消后是否保留记录。当任务只有“忙”和“闲”boolean 足够当出现渲染进度、系统确认、取消、重试和队列应升级为单一阶段状态机。无论复杂度如何增长最基本的不变量都不变第一次任务在首个 await 前占锁任务离开时无条件解锁。
返回列表