高校周边通 · 国庆特别版:HarmonyOS 7 文搜图「一日一景」功能开发实战
本文代码基于 HarmonyOS 7 / API 26 Beta2 官方文档中的原始示例撰写,函数名、字段名、错误码均来自官网最新版本(更新时间:2026-09-07)。
官方原文链接:
- textSearchImage API Reference
- 通过文本搜索图片 开发指南
项目背景:「高校周边通」是面向大学生的校园本地生活应用。2026 年国庆版本(v3.6.0)新增「一日一景」功能:用户在校园随手拍的国庆元素照片(红旗、校门灯笼、社团彩旗等),可被端侧 AI 自动建索引,用户只要输入一句话就能从相册里翻出对应照片。本文将完整解析该功能背后的
textSearchImage实现。
一、为什么"高校周边通"要在国庆版本接入文搜图?
国庆七天长假是大学生最活跃的拍摄期:迎新晚会、社团彩排、宿舍团建、city walk 打卡……用户相册会在一周内新增数百张照片。但翻照片的痛点随之而来:
- “那天食堂门口挂的红灯笼在哪张图里?”
- “国庆晚会我和室友的合影是哪一张?”
传统按时间线、文件夹浏览,效率极低。「一日一景」通过textSearchImage让用户用一句话搜照片,整个过程在端侧完成,零云端流量,零隐私泄露风险。
二、textSearchImage官方完整接口
| 接口 | 说明 |
|---|---|
textSearchImage.init(): Promise<boolean> | 初始化分析器 |
textSearchImage.release(): Promise<void> | 释放分析器 |
textSearchImage.insertImage(imagePath: string, scope: string): Promise<boolean> | 将单张图片特征插入 scope |
textSearchImage.search(query: string, scope: string, topKey: number): Promise<ImageObject[]> | 在 scope 内检索 |
textSearchImage.deleteImage(imagePath: string, scope: string): Promise<boolean> | 删除单张图片特征 |
textSearchImage.clearData(): Promise<boolean> | 清空所有 scope |
ImageObject 字段:
imagePath: string // 图片路径 scope: string // 图片作用域 similarity: number // 相似度 [-1, 1],越大越相似官方错误码:
1013100001Invalid image path.1013100002Service abnormal.1013100003The capability has been updated. Please use the new API.
三、「一日一景」功能完整实现
3.1 模块权限配置
// module.json5 { "module": { "requestPermissions": [ { "name": "ohos.permission.READ_IMAGEVIDEO" } ] } }3.2 索引管理服务(一日一景核心模块)
/** * @file DaySceneIndexService.ets * @description 高校周边通 · 一日一景 索引服务 */import{textSearchImage}from'@kit.CoreVisionKit';import{hilog}from'@kit.PerformanceAnalysisKit';import{BusinessError}from'@kit.BasicServicesKit';import{photoAccessHelper}from'@kit.MediaLibraryKit';import{fileIo}from'@kit.CoreFileKit';constDOMAIN=0x0000;constTAG='DaySceneIndex';// 索引域:按"国庆 + 高校"维度隔离,方便节后清理constSCOPE_DAY_SCENE='campus.explorer.dayscene.2026nationalday';exportinterfaceDaySceneHit{imagePath:string;scope:string;similarity:number;}exportclassDaySceneIndexService{/** * 应用启动时调用(Ability onCreate) */staticasyncinit():Promise<void>{try{constok=awaittextSearchImage.init();hilog.info(DOMAIN,TAG,`Text search image initialization result:${ok}`);}catch(error){hilog.error(DOMAIN,TAG,`Init failed. Code:${error.code}, message:${error.message}`);}}/** * 应用退出时调用 */staticasyncrelease():Promise<void>{try{awaittextSearchImage.release();hilog.info(DOMAIN,TAG,'Text search image released successfully');}catch(error){hilog.error(DOMAIN,TAG,`Release failed. Code:${error.code}, message:${error.message}`);}}/** * 将相册中的"国庆专题"照片批量索引 * @param photoUris 用户从 photoPicker 选择的 uri 列表 */staticasyncbatchIndex(photoUris:string[]):Promise<number>{letsuccessCount=0;for(consturiofphotoUris){// 1. uri 转真实路径(官方要求:必须是沙箱路径,长度 [1,128])constrealPath=awaitDaySceneIndexService.uriToPath(uri);if(!realPath)continue;// 2. 调官方 insertImagetry{constresult=awaittextSearchImage.insertImage(realPath,SCOPE_DAY_SCENE);if(result)successCount++;hilog.info(DOMAIN,TAG,`Insert${realPath}:${result}`);}catch(error){consterr=errorasBusinessError;hilog.warn(DOMAIN,TAG,`Insert failed. Code:${err.code}, message:${err.message}`);}}returnsuccessCount;}/** * 一日一景 - 语义搜索入口 * 用户输入:"操场上的五星红旗"、"食堂门口的灯笼" */staticasyncsearch(query:string,topKey:number=30):Promise<DaySceneHit[]>{try{constresults=awaittextSearchImage.search(query,SCOPE_DAY_SCENE,topKey);hilog.info(DOMAIN,TAG,`Search "${query}" count:${results.length}`);returnresults.map(item=>({imagePath:item.imagePath,scope:item.scope,similarity:item.similarity}));}catch(error){consterr=errorasBusinessError;hilog.error(DOMAIN,TAG,`Search failed. Code:${err.code}, message:${err.message}`);return[];}}/** * 删除某张图片索引(用户在相册中删除时同步触发) */staticasyncdelete(imagePath:string):Promise<boolean>{try{constresult=awaittextSearchImage.deleteImage(imagePath,SCOPE_DAY_SCENE);hilog.info(DOMAIN,TAG,`Delete${imagePath}:${result}`);returnresult;}catch(error){consterr=errorasBusinessError;hilog.warn(DOMAIN,TAG,`Delete failed. Code:${err.code}, message:${err.message}`);returnfalse;}}/** * 国庆结束后清理索引 */staticasyncclearAll():Promise<boolean>{try{constresult=awaittextSearchImage.clearData();hilog.info(DOMAIN,TAG,`Clear all data:${result}`);returnresult;}catch(error){consterr=errorasBusinessError;hilog.error(DOMAIN,TAG,`Clear failed. Code:${err.code}, message:${err.message}`);returnfalse;}}/** * photoAccessHelper uri 转沙箱路径 */privatestaticasyncuriToPath(uri:string):Promise<string|null>{try{constfile=awaitfileIo.open(uri,fileIo.OpenMode.READ_ONLY);constpath=fileIo.getFilePathFromUri(file.fd);awaitfileIo.close(file);returnpath;}catch{returnnull;}}}3.3 UI 层:国庆主题搜索页
/** * @file DayScenePage.ets * @description 高校周边通 · 一日一景 搜索页 */import{photoAccessHelper}from'@kit.MediaLibraryKit';import{DaySceneIndexService,DaySceneHit}from'../utils/DaySceneIndexService';@Entry@Componentstruct DayScenePage{@Statequery:string='';@Statehits:DaySceneHit[]=[];@StateindexedCount:number=0;// 国庆快捷检索词privatereadonlyholidayPresets:string[]=['校园里的五星红旗','食堂门口的灯笼','宿舍阳台的烟花','操场的迎新晚会','校门外的国庆彩旗'];asyncaboutToAppear():Promise<void>{awaitDaySceneIndexService.init();}asyncaboutToDisappear():Promise<void>{awaitDaySceneIndexService.release();}build(){Column(){// 国庆 BannerRow(){Text('一日一景 · 国庆特辑').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFE600')Image($r('app.media.national_flag')).width(24).height(24)}.width('100%').padding(16).linearGradient({angle:90,colors:[['#FFE60019',0.0],['#FFFF0000',1.0]]})// 一键索引按钮Button('📸 从相册导入国庆照片').width('90%').height(48).backgroundColor('#FFE60019').fontColor(Color.White).margin({top:16}).onClick(async()=>{constpicker=newphotoAccessHelper.PhotoViewPicker();constopts=newphotoAccessHelper.PhotoSelectOptions();opts.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;opts.maxSelectNumber=50;constresult=awaitpicker.select(opts);constcount=awaitDaySceneIndexService.batchIndex(result.photoUris);this.indexedCount=count;})// 索引结果if(this.indexedCount>0){Text(`已索引${this.indexedCount}张国庆照片`).fontSize(12).fontColor('#999999').margin({top:8})}// 搜索框Row(){TextInput({placeholder:'搜一句:灯笼 / 红旗 / 烟花…'}).layoutWeight(1).height(40).backgroundColor('#F5F5F5').onChange((v:string)=>{this.query=v;})Button('搜索').height(40).backgroundColor('#FFE60019').onClick(()=>this.doSearch())}.padding(16)// 国庆快捷词Flex({wrap:FlexWrap.Wrap}){ForEach(this.holidayPresets,(preset:string)=>{Text(`#${preset}`).fontSize(12).fontColor('#FFE60019').backgroundColor('#22E60019').padding(8).borderRadius(16).margin(4).onClick(()=>{this.query=preset;this.doSearch();})})}.padding({left:16,right:16})// 命中结果Grid(){ForEach(this.hits,(hit:DaySceneHit)=>{GridItem(){Stack(){Image(`file://${hit.imagePath}`).objectFit(ImageFit.Cover).width('100%').height(140)Text(`${(hit.similarity*100).toFixed(0)}%`).fontColor(Color.White).fontSize(11).backgroundColor('#99000000').padding(4).borderRadius(4)}}})}.columnsTemplate('1fr 1fr').columnsGap(8).rowsGap(8).padding(16).layoutWeight(1)}.width('100%').height('100%').backgroundColor('#FFFFFF')}privateasyncdoSearch(){if(!this.query.trim())return;this.hits=awaitDaySceneIndexService.search(this.query,30);}}四、节后运营策略
textSearchImage.clearData()会清空所有 scope,所以在节后建议:
// 国庆结束 7 天后,自动清理索引aboutToDisappear(){if(this.isNationalDayEnded()){DaySceneIndexService.clearAll();}}也可以保留索引但提示用户「索引已过期,是否清理?」,把选择权交给用户。
五、写在最后
「一日一景」是textSearchImage在 HarmonyOS 7 上的典型落地场景。它把 AI 搜索能力下沉到端侧,让学生在节日期间既能快速翻照片,又不用担心隐私泄露。这正是 HarmonyOS 7 强调的"Local-first AI"理念的最好实践。