ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

HarmonyOS 7.0 文搜图实战③:结合图库实现检索【鸿蒙心迹】

HarmonyOS 7.0 文搜图实战③:结合图库实现检索【鸿蒙心迹】

上一篇,我们把「一句找图」的一次检索拆成文件准备、初始化、图片入库和文本查询。固定的五张图片让这个过程容易理解,也掩盖了一个问题:图片不会一直固定。

用户导入一张照片,随后又移除它;应用在入库时退出;再次打开时,清单还在,检索记录是否也在?如果继续每次覆盖样本、重新入库,我们只是绕过了这些问题。

本篇让图库开始变化。我们仍使用demo01,查询上限仍为 5,保留 sea01、sea02、hill01、coffee01、dog01 作为基准样本。变化的是应用管理图片的方式:每一次操作,都要留下能够解释和恢复的记录。

1. 数据管理:三套数据

先把“图片已经导入”拆开看。应用沙箱里有图片副本,应用清单里有这张图片的身份,文搜图服务里还有通过insertImage建立的检索记录。三者承担不同职责。

数据作用不能据此推断什么
沙箱图片副本展示图片,并作为入库输入文件存在不代表已经入库
应用清单保存身份、路径和操作进度本地状态不保证服务当前状态
服务检索记录支持自然语言检索移除记录不会替应用删除文件

系统相册原图是另一个边界。本篇的“从本应用移除”,只处理应用持有的副本及其检索记录。用户在相册中选择照片,不应被理解为授权应用随意删除原图。

小型 Demo 可以用一份私有 JSON 文件保存清单。先定义最少的业务字段:

typeItemState='pending'|'ready'|'failed'|'deleting'|'cleanup'|'uncertain';interfaceGalleryItem{id:string;relativePath:string;scope:string;digest:string;state:ItemState;lastError:string;}// 应用自定义接口:Promise 完成表示本次清单写入已提交。interfaceGalleryStore{save(item:GalleryItem):Promise<void>;remove(id:string):Promise<void>;}

这些状态不是 SDK 枚举。pending表示已经登记入库意图;ready表示应用记录过成功返回;cleanup表示检索记录已确认删除,文件尚待清理。uncertain则承认我们暂时不知道服务操作的最终结果。

清单写入应集中串行处理,可以先写临时文件,再替换正式文件。读取时校验结构,并保留可恢复的旧版本。这能减少半份 JSON 的问题,却不能让文件写入与服务调用变成同一个事务。清单无法提交时,应停止后续变更,不能继续显示成功。

图1:数据关系示意。系统相册原图在应用管理边界之外;三份数据之间没有自动同步的保证。

2. 导入图片:创建副本

沿用已有页面,在搜索区下方增加“导入图片”次级入口。初版一次选择一张,足以观察完整流程。选择器负责让用户选择,文件层负责复制,文搜图负责建立检索记录。

import{photoAccessHelper}from'@kit.MediaLibraryKit';import{fileIoasfs}from'@kit.CoreFileKit';// 单次选择示例;目标路径由应用预先分配,确保未被占用。asyncfunctionselectAndCopy(targetPath:string):Promise<boolean>{constoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=1;constpicker=newphotoAccessHelper.PhotoViewPicker();constresult=awaitpicker.select(options);if(result.photoUris.length===0){returnfalse;}constsource=awaitfs.open(result.photoUris[0],fs.OpenMode.READ_ONLY);try{awaitfs.copyFile(source.fd,targetPath);}finally{awaitfs.close(source.fd);}returntrue;}

取消选择是正常结束。选出的 URI 应交给支持 URI 的文件接口,不能删掉协议前缀就把它当成本地路径。当前选择器文档说明返回 URI 具有永久授权,但授权不意味着图片永远存在;复制时仍然要处理读取失败。重复拉起选择器还需遵守文档中的实例销毁要求,上面的片段只展示一次选择过程。

复制目标使用短而唯一的文件名,例如由应用生成的图片标识。内容摘要另存一个字段,避免把长摘要直接拼入路径。copyFile默认会覆盖已有目标,所以唯一性必须在复制之前保证,不能靠复制成功来证明没有覆盖。

复制完成后,还要解码校验图片,检查宽高均大于 100、小于 10000 像素,以及完整沙箱路径满足 1~128 字符约束。扩展名不能替代这些检查。失败或不完整的副本进入文件清理流程,不能提交给文搜图。只有校验通过,才进入清单登记和入库阶段。

图2:手机界面示意,沿用白底、蓝色搜索按钮和双列卡片。导入期间搜索不可用,完成后再恢复。

3. 入库操作:成功写入

入库调用前先保存pending。这样即使进程中断,下一次启动仍能知道有一项工作没有结束。下面的store是前述应用接口,path是已校验的沙箱路径;调用由统一协调入口串行执行。

import{textSearchImage}from'@kit.CoreVisionKit';asyncfunctionindexItem(item:GalleryItem,path:string,store:GalleryStore):Promise<void>{item.state='pending';item.lastError='';awaitstore.save(item);letinserted:boolean;try{inserted=awaittextSearchImage.insertImage(path,item.scope);}catch(error){item.state='uncertain';item.lastError='入库调用异常,需核对结果';awaitstore.save(item);throwerror;}item.state=inserted?'ready':'failed';item.lastError=inserted?'':'入库返回 false';awaitstore.save(item);}

这里有一个容易漏掉的窗口:服务已经返回true,最后一次清单写入却失败了。磁盘上仍是pending,服务中可能已经有记录。外层协调器此时应停用相关操作,并提示状态保存失败,不能将它改写为“图片没有入库”。

同样,异常不总能证明服务没有执行。初版保守地记为结果不确定;以后可以根据明确的错误语义细分。false表示这次调用没有报告成功,也不应据此推断服务一定不存在历史记录。

界面不必把这些技术状态原样展示。用户只需要看到“正在导入”“导入未完成”或“需要恢复”,并有下一步操作。错误码和失败阶段保留在诊断记录中。一个诚实的提示,比没有依据的成功提示更有用。

4. 重复导入:去重检查

文件名不适合做去重依据。两张不同照片可以同名,同一张照片也可以被改名。这个 Demo 使用文件字节的 SHA-256 摘要,并限定在同一个 scope 内比较。

import{hash}from'@kit.CoreFileKit';constdigest=awaithash.hash(sandboxPath,'sha256');constexisting=items.find((item:GalleryItem)=>item.scope==='demo01'&&item.digest===digest);// existing 不存在:登记新条目。// 已存在:按其状态进入重复提示或恢复流程,清理本次多余副本。

找到同摘要条目后,还要检查状态。只有记录为ready、文件完好且当前会话已满足恢复条件,才提示“已在图库中”。遇到pending、failed、deleting或uncertain,应展示对应进度,不能把“内容相同”解释为“之前的操作已经完成”。

这种方法识别的是相同字节。照片重新压缩、裁剪或编码后,摘要可能改变,即使肉眼看起来一样。本篇接受这一边界,不引入视觉去重。

更新内容也不再覆盖旧路径。先保存为新文件并入库,确认后再移除旧版本;切换期间暂停查询。未来实现更新入口时,清单还要记录新旧条目的关联与切换进度,否则重启后无法判断哪一版应该保留。先增加再删除降低了旧图过早丢失的风险,但两个动作之间仍可能中断。

图3:手机界面示意。提示位于搜索区下方,摘要等实现信息不出现在业务页面。

5. 移除图片:先删记录,再删副本

用户选中卡片后,操作区显示“从本应用移除”。执行前说明系统相册原图会保留。我们的顺序是:保存删除意图,删除检索记录,保存待清理状态,删除沙箱副本,最后移除清单条目。

asyncfunctionremoveItem(item:GalleryItem,path:string,store:GalleryStore):Promise<void>{item.state='deleting';item.lastError='';awaitstore.save(item);constdeleted=awaittextSearchImage.deleteImage(path,item.scope);if(!deleted){item.lastError='删除记录返回 false';awaitstore.save(item);return;}item.state='cleanup';awaitstore.save(item);awaitfs.unlink(path);awaitstore.remove(item.id);}

此片段展示顺序;异常交由外层协调器捕获,停止流程并保留最后一个持久状态。检索记录删除失败时,文件还在。文件删除失败时,清单停在cleanup,下一次只需处理文件清理,不必再次调用服务删除。

如果文件已经删除,移除清单却失败,恢复时可以确认文件缺失,再完成清单清理。需要区分“文件不存在”和“暂时无法读取”,不能吞掉所有文件异常。

更困难的窗口在前面:deleteImage成功,但cleanup尚未保存就退出了。磁盘上只剩删除意图,无法证明服务调用是否完成。我们不能假设删除不存在的记录一定成功,再无条件重试;应保留移除意图,进入恢复判断或受控重建。

deleteImage、unlink、release分别处理检索记录、应用文件和服务资源,职责不同。删除之后还应清除页面中缓存的对应结果,避免旧卡片继续访问已移除的文件。

图4:手机界面示意。移除入口固定在选中卡片的操作区,提示明确保留系统相册原图。

6. 重新启动:恢复判断能力

启动不能从“一律重新插入”开始。先读取并校验清单,检查相对路径仍位于应用管理目录内,核对副本是否可用,然后初始化服务,处理未完成操作,最后决定是否开放搜索。

清单状态启动时的处理方向
ready核对文件,并依据已验证的服务持久化行为恢复
pending、uncertain结果可能未知,进入恢复判断
failed保留失败阶段,按明确原因处理
deleting保留移除意图,不重新加入图库
cleanup只完成文件与清单清理

已读接口文档没有给出足够的重复插入、删除幂等性和跨启动持久化保证。因此,ready只是应用曾记录成功,不能独自证明服务今天仍有对应记录。真机验证这些行为之前,初版应停留在“需要恢复”,提供明确的重建入口;不要偷偷把猜测写成平台契约。

也不能通过搜索“海边的落日”是否返回 sea01,判断它是否入库。相关性、返回上限和查询描述都会影响结果。检索结果不是索引清单。

第二篇的串行协调仍然适用,现在它需要覆盖初始化、导入、搜索、删除、重建和释放。同一特性不能并发调用。按钮置灰只负责表达状态,真正的互斥由调用入口保证。如果允许页面重入,就把协调器放到共享服务层,避免旧页面释放资源时,新页面已经开始查询。

图5:IDE 布局示意。右侧为静态 Previewer,不表示模拟器已执行文搜图;完整链路仍需符合条件的真机。

7. 能力更新:重新生成新数据

错误1013100003表示能力更新,官方要求清除数据后重新使用。应用需要把这条要求转成完整的恢复过程。

首先停止接收新任务,等待当前调用结束。随后持久保存重建批次、阶段以及计划保留的图片集合,把旧的ready标记为待重建。这个批次记录独立于前面的单条目结构,不能只保存在页面变量中。

// 重建阶段的核心调用;须先保存批次意图,并通过串行入口执行。constcleared=awaittextSearchImage.clearData();if(!cleared){thrownewError('清理检索数据未成功,停止重建');}// 保存“清理已成功”的批次阶段。// 按原 scope 串行插入计划保留且校验通过的图片。// 每项保存结果,最后提交批次完成状态。

clearData()没有 scope 参数,不能把它描述为“只清空 demo01”。本系列使用单一作用域;以后增加其他作用域时,必须核实清理范围,再设计恢复方案。

重建保留沙箱副本和业务清单,重新生成检索数据。待删除、待清理的条目不在保留集合中,否则恢复会把用户已经决定移除的图片重新加入图库。

中断同样可能发生在清理成功与批次落盘之间。遇到未知阶段,应保留恢复状态,根据已核实的接口行为重新制定批次,不能简单跳到下一步。本篇采用全部计划项处理成功后才开放搜索的规则;有失败就显示“重建未完成”,提供原因与恢复入口。它不能伪装成“没有找到图片”,也不应成为每次启动都执行的例行清空。

图6:界面设计示意。进度只统计实际处理的条目,不预设耗时或成功率。

8. 状态检查,再检查结果

验证分成两层。应用层用测试替身覆盖返回false、抛出异常和清单写入失败,尤其检查服务成功后落盘失败的窗口。设备层再验证真实接口、重启行为与恢复路径,记录设备、系统版本和触发条件。

至少检查新增、同内容重复导入、同名不同内容、删除中断、文件清理失败和重建中断。中断应安排在不同持久化边界,不能只测流程开始和结束。故障注入与真实设备现象分别记录。

图库恢复后,仍用“海边的落日”“窗边的一杯咖啡”“草地上的小狗”回归,保留原来的五张基准图。记录实际结果,不把一次命中当作全部数据正确的证明。

这一篇为图库建立了操作规则:新增有身份,移除有边界,中断留下进度,不知道结果时也有明确的状态。下一篇,我们才有稳定的条件讨论用户更直接的问题:同一句描述,返回的图片是否符合预期,以及等待和连续操作的体验如何改进。

返回列表