资讯动态

HarmonyOS 7.0 文搜图实战④:搜索结果优化【鸿蒙心迹】

发布时间:2026/10/24 3:58:38 来源:尧图企业网站定制
上一篇我们为「一句找图」的图库变化建立了规则。新增图片有自己的身份移除图片有明确边界操作中断后也留下了恢复的线索。现在可以把注意力放回搜索本身。输入“窗边的一杯咖啡”服务返回一个数组图片也正常显示。这说明调用链路走通了却还没有回答用户的问题那杯咖啡是否容易找到如果第一张图片不合适剩下几张也只是勉强相关页面再流畅也不能替代检索效果。本篇从两个方向推进先建立判断结果的方法再让页面准确表达一次搜索的进度与结果。仍然使用demo01返回上限仍为 5保留显式搜索按钮。我们暂时不需要更多入口需要的是更明确的规则。1. 比较查询假设修改查询后咖啡照片的位置提前了。原因可能是描述更合适也可能是刚刚删除了另一张照片。若同时改变图库和查询就很难解释结果为什么变化。因此先把 sea01、sea02、hill01、coffee01、dog01 组成的集合记为baseline-v1。这是应用自己的实验标识对应一份包含图片身份与内容摘要的清单。只有名称相同还不够同名文件的内容可能已经改变。第三篇新增的 lake01 放入扩展集。使用扩展集时另记版本不把两组结果混在一张表里比较。开始评测前先完成图库恢复评测过程中暂停增删让每次查询面对相同的数据。查询也需要固定。前三篇的三句话继续保留在此基础上增加少量变体查询观察目的海边的落日保留基准描述窗边的一杯咖啡保留基准描述草地上的小狗保留基准描述海边日落时的浪花观察场景细节的影响窗边木桌上的白色咖啡杯观察属性细化的影响雪地里的红色汽车观察图库没有预期目标时的返回先查看原图再确定哪些图片符合每句话。文件叫 coffee01并不代表它一定包含窗户、木桌和白色杯子。图库外的描述也不保证返回空数组如果服务给出弱相关图片这本身就是需要记录的现象。用简单的数据结构保存这份约定import{textSearchImage}fromkit.CoreVisionKit;interfaceQueryCase{id:string;query:string;galleryVersion:string;expectedIds:string\[];}interfaceEvaluationRun{queryId:string;galleryVersion:string;rawResults:textSearchImage.ImageObject\[];}expectedIds由看图后的判断填写不能从返回结果反推。每轮记录还应带上设备、系统、SDK 和执行时间。这样后续看到不同结果时我们至少知道比较的条件是否相同。图1保持图库不变分别观察三条固定查询的结果每个页面保留对应的查询文字。2. 相关性与相似度“这张图看起来还行”很难用于回归。今天可以接受的结果明天可能又觉得不够准确。判断标准应当写在查询旁边并在查看排名之前确定。初版分成三级符合描述、部分符合、不符合。例如查询同时包含主体和场景时可以要求两者都满足才算严格相关只有主体一致则记为部分符合。一条查询允许对应多张图片不能因为心里先想到 sea01就把另一张符合描述的海边照片判错。先观察两个量就够了。第一是首项是否严格相关它接近用户打开结果后的第一印象。第二是返回候选中的严格相关比例即严格相关张数除以实际返回张数。部分符合的结果保留备注不混进严格相关的数量里。空数组的首项记为未命中候选相关比例记为不适用。接口异常另列调用失败同时报告有效调用数否则把失败请求全部删掉统计表会显得过于漂亮。评测服务原始结果和应用排序结果时也要注明采用了哪一种顺序。这套小图库适合观察回归。它只有五张图片而返回上限也是五张检查目标是否“出现在某处”区分度有限。目标排在哪里、前面有多少不合适的图片往往更值得关注。少量查询不能支持整个能力的准确率结论。similarity可以帮助解释排序。官方定义的范围为 [-1, 1]值越大相似度越高。但它不是概率把 0.8 显示成“80% 匹配”会给用户一种并不存在的确定性。不同查询的分数也不应直接作为同一尺度比较。因此本篇不急着加硬阈值。一个阈值可能挡住不合适的图片也可能一起挡住用户要找的照片。后续若尝试阈值应保留一组没有参与调参的查询同时检查误放与漏掉的结果。图2增加描述细节后观察返回内容和顺序的变化描述更长不意味着结果必然更好。3. 排序顺序第二篇按接口返回顺序展示没有假设服务保证某种排序。本篇增加一个明确的应用策略对已返回候选按相似度降序排列同分时保持原位置。interfaceRankedCandidate{image:textSearchImage.ImageObject;originalIndex:number;}functionorderCandidates(raw:textSearchImage.ImageObject\[]):textSearchImage.ImageObject\[]{constranked:RankedCandidate\[]raw.map((image:textSearchImage.ImageObject,index:number):RankedCandidate{return{image:image,originalIndex:index};});ranked.sort((a:RankedCandidate,b:RankedCandidate):number{constdifferenceb.image.similarity-a.image.similarity;returndifference!0?difference:a.originalIndex-b.originalIndex;});returnranked.map((entry:RankedCandidate)entry.image);}// 放在共享协调器管理的搜索任务内。constrawawaittextSearchImage.search(query,demo01,5);constorderedorderCandidates(raw);排序使用新的数组原始顺序仍可用于分析。它只调整当前候选的位置不会找回服务没有返回的图片。topKey是返回数量上限不保证凑满五张也不是相关性阈值。展示前还要把路径和 scope 对应回第三篇的图片清单。已经移除、需要恢复或文件不可读的条目不能作为正常图片展示。每次排除都应保留原因否则页面只剩两张图时我们无法判断是服务只返回两张还是另外三张在展示阶段出了问题。实际处理因此有三层服务原始返回、应用排序与校验后的候选、页面成功显示的图片。若原始数组非空但所有文件都无法显示应提示图片加载问题把它写成“没有找到结果”会把文件故障误报为相关性问题。图3编辑区保留排序逻辑手机页面继续使用双列图片卡片诊断信息留在开发记录中。4. 查询结果对照搜索还没结束用户已经把“海边的落日”改成了“草地上的小狗”。这时返回的海边照片究竟属于哪句话如果页面只显示当前输入框用户很容易把它理解成一次错误检索。为此把输入草稿和已提交查询分开。draftQuery随输入变化submittedQuery只在接受一次新搜索时更新。结果区显示“本次查询”明确它对应哪个请求。typeSearchPhaseidle|searching|results|empty|error|recovery|displayError;interfaceSearchViewState{draftQuery:string;submittedQuery:string;phase:SearchPhase;images:textSearchImage.ImageObject\[];message:string;}这是页面状态与第三篇单张图片的ready、pending分开管理。一张图片就绪不能证明整个图库已完成恢复。提交前先去掉首尾空白检查非空和长度约束。查询参数允许长度为 1100文档参数表不支持纯数字、纯字母查询这里继续使用中文描述。产品侧长度检查还需与接口的字符计数口径对齐尤其不要把表情等字符的视觉数量直接当作字符串长度。新搜索被接受后清空上一轮结果保存提交文字再进入等待状态。查询期间允许编辑草稿但搜索和导入按钮置灰。待当前任务结束用户再次点击才会提交新的草稿。这使每次调用都有清楚的起点也无需为了连续输入引入防抖。图4输入框保存草稿结果区标明本次查询搜索期间的搜索和导入按钮统一置灰。5. 回写检查按钮状态负责沟通协调器负责约束。快速点击可能发生在界面更新之前因此忙碌状态要在第一个await之前登记。当前任务未结束时重复提交直接忽略不积压一串用户早已不关心的查询。继续复用第三篇的共享协调入口。初始化、搜索、导入、删除、重建和释放都经过它同一特性不并发调用。不要为搜索另建一把锁再让图库操作使用另一把锁。串行调用解决了服务重叠却没有自动解决页面离开后的回写。请求发出后页面可能关闭下一次打开也可能产生新会话。为一次请求记下三个应用侧标识请求编号、会话编号和图库修订号。interfaceSearchTicket{requestId:number;sessionId:number;galleryRevision:number;}interfaceSearchContext{requestId:number;sessionId:number;galleryRevision:number;closed:boolean;}functioncanPublish(ticket:SearchTicket,current:SearchContext):boolean{return!current.closed \\ticket.requestIdcurrent.requestId \\ticket.sessionIdcurrent.sessionId \\ticket.galleryRevisioncurrent.galleryRevision;}协调器接受任务时生成 ticket异步处理完成后再用最新上下文校验。只有仍然有效才提交候选结果和页面状态。图库发生变更或进入恢复时推进修订号页面离开时关闭会话。这里的运行期修订号用于识别过期请求与前面的实验版本承担不同职责。检查不能只放在成功分支。异常提示也可能来自旧请求finally中若无条件把页面设为可用更可能把新会话的等待状态清掉。页面回写必须验证归属协调器则始终结束自己持有的任务并通知当前会话重新计算可用状态。丢弃回写并不取消底层调用。页面离开后仍要等待正在执行的工作结束再按既定顺序释放服务。这样界面生命周期和服务生命周期各自有明确的收尾。6. 空结果、调用失败与需要恢复这三种情况都可能让页面没有图片但用户需要采取的动作不同。情况页面表达下一步接口返回空数组本次未找到结果修改描述后搜索接口调用失败搜索未完成按错误原因处理或重试图库需要恢复请先恢复图库进入第三篇的恢复流程图片加载失败部分图片无法显示检查副本与清单状态错误1013100003应转入第三篇的能力更新恢复流程而不是普通重试。其他错误也保留阶段和错误码业务页面使用可理解的文字。用户修改了草稿后失败提示仍要保留原提交查询“重试本次查询”应使用原文字新搜索按钮才提交当前草稿。还有一种情况不属于上表服务返回了图片但用户认为不相关。此时保留候选让评测记录说明问题。没有充分的阈值依据就不自动把它归为“没有结果”。图5状态提示放在原有搜索区下方不增加导航不同原因对应不同操作入口。7. 等待时间要比较等待体验先定义计时边界。search()的调用时长从调用前到 Promise 完成用户可见等待时间则从提交被接受开始还包含应用处理和首屏图片呈现。数组返回了不代表图片已经出现在屏幕上。计时使用单调时钟避免系统时间校准影响差值。可以把时间来源封装成应用接口使测量逻辑与平台适配分开// 应用接口实现方提供单调递增、单位为毫秒的时间值。interfaceMonotonicClock{nowMs():number;}interfaceTimedSearchResult{images:textSearchImage.ImageObject\[];serviceMs:number;}asyncfunctionmeasureSearch(query:string,clock:MonotonicClock):PromiseTimedSearchResult{conststartedclock.nowMs();constimagesawaittextSearchImage.search(query,demo01,5);constfinishedclock.nowMs();return{images:images,serviceMs:finished-started};}这个函数替换共享搜索任务内的直接调用不额外发起一次搜索。平台适配层选择目标 SDK 支持的单调时钟并统一单位不使用会受用户改时影响的日历时间代替它。异常耗时另记为失败记录不混入成功调用的耗时分布。初始化、入库和重建分别计时。首轮与后续查询也分别记录条件不把观察到的差异直接归因于某种缓存。少量重复测试先保存原始值、样本数和中位数比给出缺少样本支撑的高分位指标更容易解释。若测量首屏等待还需为图片完成或失败的回调绑定同一个 ticket明确何时认为首屏处理结束。没有图片时单独记录空状态完成时间不能把它当作一次更快的图片展示。图6计时逻辑留在开发侧主界面只表达搜索进度服务返回与首屏呈现采用不同的结束点。8. 判断方法这一轮检查仍从三条基准查询开始再加入近义改写、细节描述和图库外描述。每条记录保留图库版本、原始返回、展示顺序及人工判断后续修改才有可比较的依据。交互检查则覆盖空白与超长输入、快速重复点击、查询中修改草稿、查询中离开页面以及图片加载失败。测试替身适合控制空数组、异常和延迟检验状态转换检索相关性、设备行为与耗时放到满足条件的真机上采集。Core Vision Kit 不支持模拟器DevEco 的手机预览用于检查布局。当一次搜索有了明确的输入、判断标准和页面状态改进就不再依赖对几张截图的印象。我们知道结果来自哪次查询也知道问题发生在检索、展示还是恢复阶段。最后一篇将整理这些决定围绕工程结构、真机验收和交付检查把「一句找图」收束成一份可以继续维护的 Demo。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑