HarmonyOS 7 AtomicServiceSearch:文搜图输入预上屏提交代次隔离【鸿蒙心迹】 同一个搜索框里用户输入“秋天”随后补成“秋天的银杏”肉眼看来不过是多敲几个字。放到真实相册检索问题并不在搜索按钮本身输入法可能仍处于预上屏阶段前一次检索可能尚未返回用户也可能连续点击两次提交。要是把每次onChange都视为一次正式查询再按回包顺序覆盖屏幕结果区就会在两个不同查询之间跳来跳去。界面上看到的关键词是银杏下面却短暂出现“秋天”的泛化结果这种错位比一次慢查询更让人难以信任。这一篇只讨论输入与结果之间的提交协议不重复语义向量模型训练、照片Embedding生成、索引分桶或瀑布流缩略图回填。项目叫QueryCommitLab主页面QueryPage诊断页面SubmitAuditPage负责把草稿文字、正式提交和异步结果拆成三条不同的时间线。示例数据集album_semantic_31任务编号QRY-1010-18。固定事件中有六次草稿变化、三次提交动作、两次有效提交、一次重复提交忽略、一次旧结果忽略最后一版提交代次为3最终展示七张“秋天的银杏”照片业务状态RESULT_READY。这些都是应用层演练数据真实检索引擎在本文标记为FIXTURE_ONLY。一、从“文字已经显示”到“查询已经生效”之间还有一道边界1. 输入框显示值不是请求凭证移动端搜索框里有至少三种值得分开的状态。第一种是输入草稿编辑、删除、输入法候选词改变都会影响它第二种是用户正式确认的查询通常由键盘搜索键或搜索按钮触发第三种是已经取回并通过当前版本校验的结果。三种状态可能在不同毫秒内发生不能用一个query字符串代表全部生命周期。尤其是输入法预上屏机制存在时按键、候选上屏、组件事件与用户意图不必恰好一一对应。华为文档中的AtomicServiceSearch提供搜索区search配置含onChange、onSubmit和enablePreviewText等能力。这个事实可以作为界面事件的锚点但它本身并不替业务决定哪次回包更新UI。我们的约束比较保守onChange只维护显示用草稿onSubmit才生成新的业务提交版本。预上屏发生时不自行猜测“候选词已定稿”也不假定框架一定提供额外的合成开始或结束回调。关于输入法的细节业务只认最后一次确认提交的字符串。本轮示例将草稿变化统计为六次它不等同于六次网络查询。用户可以连续修改草稿而真正创建请求的次数只有两次。第三次onSubmit因与最新已提交文本相同被业务去重。这里的“相同”在约定范围内只做首尾空白清理和连续空白折叠不把繁简体、同音词或语义相近的句子擅自归为同一个检索任务。若产品确实需要语言归一或纠错应把具体规则和版本放到独立的检索规范里。2. 为什么不在每个 onChange 都发请求按变化实时预取并非绝对错误问题在于不能将预取与正式提交混为一谈。一旦多个输入事件都带来异步任务任务取消、输入法预上屏、焦点变化和页面切换会引出更多生命周期分支即使做了节流较旧的请求也可能在较新的请求之后返回。节流解决请求频率不能替代结果归属。为了先把工程边界讲清本例把预取功能关闭待提交协议稳定后再考虑把预取做成“只能暖缓存、不得直接渲染”的第二条通道。本例的确定性事件顺序是09:18:10提交“秋天”得到代次209:18:12提交“秋天的银杏”得到代次3同一时刻重复提交银杏被忽略09:18:13代次2的旧结果返回但不得写屏09:18:14代次3返回七张图并应用。提交编号从1开始留给页面基线因而两次有效提交后的queryRevision为3。代次与请求标识不是从时间戳截取出来的“看起来唯一”字符串而是同一页面会话内递增的整数页面销毁重建时需要同时更换会话身份防止旧实例的回调碰巧与新实例撞号。二、给业务提交加一个不依赖组件的闸门先处理核心问题如何让重复提交不产生新版本以及如何保证旧回包不能覆盖新查询。把这部分写成普通ArkTS类可单独注入固定输入不必先启动DevEco设备。接口只接收已确认提交的文字返回undefined表示重复或空文本返回不可变快照表示新任务。accepted是业务计数不代表平台搜索成功。interfaceQueryTicket{sessionId:string;revision:number;text:string;}classQueryCommitGate{privaterevision:number1;privatelastCommitted:string;privatesessionId:stringQRY-1010-18;accepted:number0;duplicateIgnored:number0;staleResultsIgnored:number0;privatenormalize(input:string):string{returninput.trim().replace(/\s/g, );}commit(raw:string):QueryTicket|undefined{constnormalizedthis.normalize(raw);if(normalized.length0){returnundefined;}if(normalizedthis.lastCommitted){this.duplicateIgnored;returnundefined;}this.lastCommittednormalized;this.revision;this.accepted;return{sessionId:this.sessionId,revision:this.revision,text:normalized};}mayApply(ticket:QueryTicket):boolean{constokticket.sessionIdthis.sessionIdticket.revisionthis.revision;if(!ok)this.staleResultsIgnored;returnok;}getcurrentRevision():number{returnthis.revision;}getlatestText():string{returnthis.lastCommitted;}close():void{this.sessionIdthis.sessionId-closed;}}这个类刻意不提供“等待某个定时器再决定是否提交”的内部逻辑。按键确认为哪次事件属于页面组件与业务控制器的责任闸门只对已经到来的提交做规则判断。它也没有假装能停止已经发给外部服务的请求mayApply的作用只是禁止旧结果进入当前状态。真实请求如果具备取消接口当然可以额外释放后台成本但取消不应当是唯一正确性保障取消与回调可能同时发生服务端也可能已经处理完任务。close()将当前会话作废解决页面退出后的迟到回调。真正工程里建议使用随机会话ID或UIAbility生命周期生成的单调序列示例拼接-closed只是便于展示可验证的失效规则。若状态需要跨进程恢复还应该存储提交快照和业务数据版本不能把页面临时内存当作持久化协议。重复提交的判断也可以拓展为“相同文本相同筛选器同一数据集修订”否则用户主动调整排序却被误判为重复。三、AtomicServiceSearch 的事件只负责事件不替数据背书页面侧需要解决两个具体动作六次草稿变动要即时反映输入框正式提交则交给闸门决定是否安排搜索。示例使用文档中公开的AtomicServiceSearch与SearchParams搜索区事件。为免把未确认模型SDK写成可直接运行的工程事实异步返回由本地FixtureSearchPort生成把它换成真正文搜图SDK前必须核对该SDK的签名、权限、索引准备状态和回调线程。import{AtomicServiceSearch}fromkit.ArkUI;interfacePhotoHit{id:string;label:string;}classFixtureSearchPort{asyncsearch(ticket:QueryTicket):PromisePhotoHit[]{// 固定夹具这里只表达返回数据不实际调用系统相册或AI模型。if(ticket.text秋天的银杏){return[P01,P02,P03,P04,P05,P06,P07].map((id:string)({id,label:银杏主题照片}));}return[{id:OLD01,label:秋天泛查询}];}}EntryComponentstruct QueryPage{Stateprivatedraft:string;StateprivateresultCount:number0;StateprivatestateText:stringREADY;privategate:QueryCommitGatenewQueryCommitGate();privateport:FixtureSearchPortnewFixtureSearchPort();privateasyncsubmitQuery(text:string):Promisevoid{constticketthis.gate.commit(text);if(!ticket)return;this.stateTextLOADING;try{consthitsawaitthis.port.search(ticket);if(!this.gate.mayApply(ticket))return;this.resultCounthits.length;this.stateTextRESULT_READY;}catch(_err){if(this.gate.mayApply(ticket))this.stateTextRETRYABLE;}}aboutToDisappear():void{this.gate.close();}build(){Column(){AtomicServiceSearch({value:this.draft,placeholder:描述你想找的照片,search:{enablePreviewText:true,onChange:(value:string){this.draftvalue;},onSubmit:(value:string){this.submitQuery(value);}}});Text(结果${this.resultCount}/${this.stateText});}}}在框架层onSubmit不需要和按钮onClick争夺“唯一提交入口”。如果外部另有实体搜索按钮让按钮调用同一个submitQuery()即可不要复制另一套异步处理路径。输入框焦点失去后是否继续显示键盘与查询版号无关屏幕旋转、折叠展开或返回页面同理。如果主页面将来被导航栈缓存而不销毁要区分“隐藏”与“释放”隐藏时是否接受回包取决于产品选择但不应绕过会话校验。若页面明确离开且不再接受结果就作废该会话返回时用新的会话重新建立视图。注意示例代码中结果数量没有在onChange被直接清零。这是设计上的选择输入草稿变化但尚未提交时历史结果仍代表上次已提交文本可以用UI标签“当前结果对应上次提交”明确提示。若产品希望即时清空也应把“空状态”标注为未提交草稿不能展示为新查询的空结果。当前演示图中只展示最后提交完成的结果态以免将中间态误认为真机截图。四、让结果图与诊断图承担不同证据上图是白色DevEco Studio风格的拟真演示图左目录、中编辑器、右模拟器、底部HiLog。它不是DevEco实际编译结果。调试时真正重要的是三个日志字段必须同时出现提交版本、对应查询、是否允许写屏。只打印“搜索成功”没有归属证据只打印回调时间又容易把后返回误认为更新的任务。本例在HiLog样例中保留QRY-1010-18 submits3 accepted2 duplicate1、revision3 staleResult1 results7 RESULT_READY、engineFIXTURE_ONLY这三个结果与业务闸门的计数含义一致。七张图片用的是固定夹具中的可视化示意卡片不存在真实的语义相似度分数、云端推理耗时或相册扫描记录。这里没有理由为截图编造“向量召回率97%”之类无法验证的数字。真正接入检索服务后结果集还应当带模型版本、索引快照号、分页游标、授权结果和可能的空图库条件。业务规则可以在同一个QueryTicket上扩展字段但应该在提交时一次冻结不能回包到了才读取当时最新的过滤器否则前后两次查询仍可能混淆。主运行示意页显示QRY-1010-18、album_semantic_31、六次输入修改、三次提交、两次接纳、一次重复提交忽略、一次迟到结果忽略、提交代次3和七张结果。真正的价值不是这些样例数值本身而是它们能构成一份互相核对的账本。比如如果提交事件计数是3接受2、重复忽略1且没有空文本事件那么两边必须相加等于3迟到结果计数单独来自结果层不得拿来凑提交层的算术。诊断页要保留时序而不重复贴七张照片。第一条“秋天”在09:18:10被接纳为代次2第二条银杏查询在09:18:12升级为代次3第三次银杏提交没有新代次09:18:13代次2回包只记STALE09:18:14代次3回包七条记APPLY。当用户反馈“输入银杏却看到了旧照片”我们先看这五条事件是否完整再查具体结果集而不是一上来怀疑索引模型语义能力。设计上只有在后端结果已正确归属后相关度质量才值得被讨论。五、用固定夹具先暴露状态协议的漏洞1. 不能拿真实异步顺序碰运气测试夹具应允许指定每条结果的返回顺序让旧查询刻意晚于新查询完成。如果只在网络良好、单次键盘提交的情况下“手工点两下”大部分竞态不会出现。第一组用例验证相同文本重复提交不增加版本第二组验证旧结果即使最后返回也不能写UI第三组验证空字符串、全空白和首尾空白处理第四组验证页面离开后会话失效第五组验证筛选器改变时必须生成新提交标识。每组都应当同时断言状态和计数不能仅检查最终列表是否非空。这里最容易写错的是“判定顺序”。例如mayApply失败时若先更新resultCount再返回屏幕仍会短暂闪出错误数量。应将会话与代次校验放在任何业务状态写入之前错误回调同样校验因为上一轮搜索失败也不应该把当前已成功的新查询改成RETRYABLE。如果未来还有分页追加或图片懒加载分页游标也须绑定已提交版本不能只比较用户界面中显示的文本。再看页面关闭组件释放并不等于异步回调对象一定不再可达。Promise保留闭包的时间取决于任务完成故而页面若提前退出回调仍可能抵达。close()作废会话能够防止逻辑写入但如果还持有实际PixelMap、文件句柄或网络连接仍需要在业务层实现对应的显式取消与释放。前者维护状态正确性后者控制资源成本两者不要互相替代。2. 搜索范围与隐私访问另外验收文搜图意味着照片可能来自用户私有图库。本文没有读取真实资产也没有假定授权一旦给出就永久有效。项目接入媒体库时实际服务应当处理用户取消选择、权限被撤回、图片被删除、跨设备资源暂不可达等返回条件。搜索UI不可把数据集名称当成权限凭证album_semantic_31只是模拟素材集标识。真实产品还要明确索引是否在端侧、检索是否上传文本、上传前如何做敏感词与隐私声明这些不属于本篇可声称已经验收的范围。同样不能把enablePreviewText误解为业务已获得“输入法合成开始”“候选词最终确认”的完整协议。它描述组件对预上屏文本的支持不保证每一种输入法都有相同的事件数量。最稳妥的写法就是让提交归属独立于按键次数即使未来接入语音输入、OCR提取或快捷搜索建议只要全部走同一业务提交闸门旧结果也无法覆盖新结果。工程收益来自协议收口而非某个输入框拥有神奇能力。六、交付前要把哪些数据写进验收证据应用日志至少应包括任务ID、会话ID、提交代次、提交时归一后的字符串摘要、对应的结果集版本及裁决原因为了避免日志泄露私人搜索词生产日志可以只存哈希、长度和合法化的业务标签全文仅在开发演示夹具中使用。日志必须足以区分重复提交与过期结果前者发生在commit后者发生在mayApply它们分别影响提交次数与回包次数。若两个计数混在一个ignored字段里现场排障会失去定位价值。页面侧还应当在空结果、报错重试和页面快速来回切换时给出明确交互反馈。空结果意味着当前有效提交返回零个匹配不是输入草稿为空错误重试要复用还是生成新版本需有显式策略。当前方案建议重试沿用原QueryTicket前提是它仍为当前版本用户一旦提交了新文本旧重试立即失去写屏资格。这个决定看上去琐碎却能避免“重试”按钮在不知不觉中把用户拉回上一段查询。最终夹具应输出一份可重放的事件记录包含六次草稿变化、三次提交、一次重复忽略、两次有效任务、一次旧结果丢弃和七条最新结果。这份记录的质量高于某张成功截图因为它不仅描述最终状态也解释最终状态从何而来。不过它也只验证了应用层函数不等于HarmonyOS设备输入法兼容性或真正语义搜索准确率。后续真机验收应覆盖中英文混输、拼音候选、多窗口焦点切换、弱网回包乱序、权限撤销及性能基线在这些测试完成之前发布文章应始终把本例称作设计与固定输入演练。还可以补上一项常被忽视的无障碍验收输入时朗读焦点仍属于搜索框结果更新后不应强制将焦点跳到第一张照片更不能因为旧请求返回而触发无意义的列表重排提示。状态播报应绑定正式提交成功后的结果版本并让用户自主进入结果列表。对键盘、触控笔和语音输入三种入口最好分别记录可重现的提交序列不同入口只改变草稿的形成方式不改变最终版本归属。此外在折叠屏外屏向内屏切换期间搜索框实例可能重建诊断时需把页面会话身份纳入日志否则看到的“版本3”未必来自同一个界面实例。这样的验证关心的是信息可理解性而不只是没有发生技术异常。七、收束不要让展示文字和数据责任缠在一起这一轮没有试图实现一个更强的AI模型甚至没有接入真正的语义检索SDK。它只是把一句看起来普通的“秋天的银杏”拆成可审计的提交快照与结果回写协议。AtomicServiceSearch负责接收输入事件QueryCommitGate负责决定提交是否有效结果服务负责返回对应版本页面只在最终校验通过后更新列表。模块职责清楚之后未来叠加索引、推荐、筛选或跨设备能力才有可靠的起点。比较合理的下一步不是立刻追求更快而是先在DevEco和真机上复现输入法预上屏、重复回车、返回页重建和弱网乱序。只有当日志能解释每一次没有写屏的原因搜索页面才算具备工程上可维护的边界。本文七张图片、代次3和RESULT_READY都只是示意演练结果不应被当成实际项目的性能或功能认证。参考资料华为 AtomicServiceSearch 公开API2026-09-04华为文档中心。