HarmonyOS 7 核心实践:基于 CoreVisionKit 端侧 AI 语义搜图与多模态双链 Vault 架构

一、 引言与业务背景

随着移动端个人知识库(PKM)与“第二大脑”(Second Brain)概念的流行,Obsidian 风格的 Markdown 纯本地双链笔记逐渐成为高效能人士的标配。然而在移动端体验中,纯文本笔记存在一个长期的痛点:多模态图片附件管理极难搜索与关联。用户在笔记中插入了大量的应用截图、设计草图、发票证件或生活照片,事后想要查找某张包含特定视觉内容的图片,传统方法只能依赖死板的文件名或手动 Tag 标记。

借助 HarmonyOS 7 提供的端侧 AI 视觉能力库 CoreVisionKit (@hms.ai.vision.textSearchImage),我们在 AeroPlan 鸿蒙版应用中成功实现了 完全脱离网络、零隐私泄漏、基于 NPU 硬件加速的端侧自然语言语义搜图(例如搜索“猫咪”、“机票”、“发票”或“架构图”)。

效果图展位:端侧 AI 语义搜图与笔记关联实测

HarmonyOS 7 核心实践:基于 CoreVisionKit 端侧 AI 语义搜图与多模态双链 Vault 架构.gif

本文将深度剖析我们在落地“端侧 AI 语义搜图 + 多模态双链 Vault 架构”过程中遇到的真实技术挑战、架构设计方案及最终解决方案。


二、 系统整体架构设计

整个系统的多模态图片管理与端侧 AI 搜索架构分为:

鸿蒙 NEXT 系统能力层 (HarmonyOS NEXT)

核心逻辑与图谱映射层

UI 交互层 (ArkUI)

绝对路径 & 纯英文 Scope

物理拷贝与物理删除

GlobalVaultSearchModal
全局 AI 语义搜图弹窗

BrainView
文件树 & attachments 附件管理

ObsidianBottomBar
右下角单手 👁️预览/✏️编辑 切换

BrainImageSearchManager
端侧 AI 引擎调度 & 搜图

VaultManager
沙箱读写 & 附件拷贝 & 文本扫描

Reverse Index Graph
图片 ➔ Markdown 笔记反向双链图谱

CoreVisionKit
textSearchImage 端侧 NPU 语义特征抽取

FileSystem Sandbox
el2/base/files/AeroPlanVault/attachments/


三、 CoreVisionKit 端侧 AI 搜图的落地与“避坑”指南

在对接鸿蒙原生 @hms.ai.vision.textSearchImage 接口时,由于端侧 NPU 特征数据库与底层 C++ 引擎存在严格的校验规则,我们总结了以下 4 个必须规避的坑点:

1. Scope 命名限制:必须为纯英文字母与数字(1-32字符)

  • 踩坑现象:传入 scope: "knowledge_brain" 会直接引发 NPU 引擎报错 Code: 401, Message: The parameter check failed.
  • 原因分析:CoreVisionKit 底层数据库对 scope 命名做正则校验,严禁包含下划线 _、减号或特殊字符
  • 解决方案:统一收敛为干净的纯英文标识符(例如 'aeroplan'):
private sanitizeScope(scope: string): string {
  if (!scope) return 'aeroplan';
  // 严格过滤非字母数字字符,限制长度在 1-32 位
  const clean = scope.replace(/[^a-zA-Z0-9]/g, '');
  return (clean.length > 0 && clean.length <= 32) ? clean : 'aeroplan';
}

2. 传入 NPU 的图片路径:必须剥离 file:// 协议头

  • 踩坑现象:直接将 ArkUI 使用的 file:///data/storage/... 路径传给 textSearchImage.insertImage(),会导致报错 Code: 1013100001, Msg: Invalid image path or size.
  • 解决方案:传给 NPU 的物理路径必须是干净的系统绝对路径(如 /data/storage/el2/base/files/AeroPlanVault/attachments/xxx.png);而在 ArkUI Image() 组件渲染缩略图时,又必须拼回 file:// 前缀。
// 传给 CoreVisionKit 引擎
const realAbsPath = '/data/storage/el2/base/files/AeroPlanVault/attachments/Image_1.png';
await textSearchImage.insertImage({ scope: 'aeroplan', imagePath: realAbsPath, noteTitle: '笔记.md' });

// 传给 ArkUI Image 渲染
const uiSrc = `file://${realAbsPath}`;
Image(uiSrc)

3. NPU SQLite 特征库防死锁:全量同步前显式 clearData()

  • 踩坑现象:多次重新同步沙箱图片后,搜索接口返回结果集突变为 0
  • 解决方案:在每次执行沙箱 attachments/ 全量图片特征重建时,先调用 textSearchImage.clearData() 重置端侧 SQLite 特征表:
public async syncAttachmentsFromVault(attachmentFiles: string[], attachmentsDir: string): Promise<number> {
  // 1. 先重置端侧特征表,防止旧索引冲突
  try {
    await textSearchImage.clearData();
  } catch (e) {
    hilog.warn(0x0000, 'BrainImageSearch', `[ClearData Error] ${JSON.stringify(e)}`);
  }
  
  // 2. 依次插入新图片绝对路径与关联元数据
  // ...
}

四、 多模态图片与 Markdown 笔记的反向双链关联图谱

在实际应用中,单纯“搜出图片”是不够的。用户在搜到一张应用截图后,核心诉求是“这张图片是在哪篇笔记里被引用的?点击能否直接切回笔记?”。

反向双链关联与 AI 搜索流程图

Yes

No

Yes

No

用户选择/插入相册图片

VaultManager 拷贝至 attachments/ 沙箱目录

扫描 Vault 所有 .md 笔记正文

正文是否包含图片文件名?

建立图谱映射: Image ➔ 真实笔记标题

标记映射: Image ➔ '未关联笔记'

调用 CoreVisionKit.insertImage 写入 NPU 索引

用户触发文本搜索: 如 '猫咪'

NPU 端侧向量计算返回匹配 Image

IsRealReferencedNote?

展现缩略图 + 📖 打开笔记 → 按钮

展示缩略图, 隐藏打开笔记按钮

点击跳转至底层 Markdown 笔记
搜索弹窗常驻保留

为此,我们在 BrainImageSearchManager 中设计了 图片 -> Markdown 笔记的反向双链图谱(Reverse Index Graph)

// 1. 扫描 Vault 下所有 Markdown 笔记正文
const noteToImageMap: Map<string, string> = new Map();
const allNotes = await VaultManager.getInstance().listAllNotes();

for (let i = 0; i < allNotes.length; i++) {
  const noteTitle = allNotes[i];
  const noteContent = await VaultManager.getInstance().readNote(noteTitle);
  if (noteContent) {
    const lowerContent = noteContent.toLowerCase();
    for (let j = 0; j < attachmentFiles.length; j++) {
      const attFile = attachmentFiles[j];
      // 检测 Markdown 正文是否包含该图片文件名
      if (lowerContent.includes(attFile.toLowerCase())) {
        noteToImageMap.set(attFile, noteTitle);
      }
    }
  }
}

// 2. 注册精准的笔记关联关系至 NPU 搜图索引
const realLinkedNote = noteToImageMap.get(fileName) || '未关联笔记';
this.imageToNoteMap.set(realAbsolutePath, realLinkedNote);
await this.insertImage(realAbsolutePath, realLinkedNote);

搜索弹窗 UX 闭环:

  1. 动态显隐:若图片已被 Markdown 引用,右侧展示 📖 打开笔记 →;若未被引用,则自动隐藏,绝不误导用户。
  2. 常驻体验:点击 📖 打开笔记 → 跳转至底层的 Markdown 编辑器,搜索弹窗保持常驻,方便用户连续查阅多张关联图片。

五、 解决跨页面/路由生命周期的响应式刷新痛点

在 ArkUI 的单页多路由架构中,用户在 NoteDetailView(笔记编辑页)中插入相册图片后点击返回退出,常遇到“主界面 attachments/ 列表不展示新图片,必须重启 App”的问题。

针对这一问题,我们构建了 “即时 Event 广播 + 路由 Lifecycle 钩子” 的双重保障机制:

双重保障响应式刷新时序图

BrainView (主界面) Index.ets (NavDestination) AppStorage (全局状态) MarkdownEditor (编辑页) BrainView (主界面) Index.ets (NavDestination) AppStorage (全局状态) MarkdownEditor (编辑页) 机制一:毫秒级状态广播 机制二:路由 Lifecycle 闭环兜底 用户 插入相册图片 Image_xxx.png 1 AppStorage.setOrCreate('lastInsertedAttachment', filename) 2 @Watch('onAttachmentInserted') 响应 3 refreshVaultData() 刷新 [...attachments] 4 点击返回退出编辑页 5 NavPathStack.pop() 页面退栈 6 NavDestination.onHidden() 触发 7 setOrCreate('lastInsertedAttachment', exited_timestamp) 8 触发强制二次同步 9 主界面实时展现最新图片列表 (无需重启 App) 10 用户

1. 插入瞬间触发 AppStorage 广播

// MarkdownEditor.ets 中完成图片物理拷贝后
const permanentUri = await VaultManager.getInstance().copyAttachment(imageUri, filename);
AppStorage.setOrCreate('lastInsertedAttachment', `${filename}_${Date.now()}`);

2. 主视图通过 @Watch 联动更新

// BrainView.ets
@StorageProp('lastInsertedAttachment') @Watch('onAttachmentInserted') lastInsertedAttachment: string = '';

async onAttachmentInserted() {
  hilog.info(0x0000, 'BrainImageSearch', `[AppStorage Event] New image inserted: ${this.lastInsertedAttachment}`);
  await this.refreshVaultData();
}

async refreshVaultData() {
  const attachments = await VaultManager.getInstance().listAttachments();
  // 必须重新赋予新解构数组引用 [...attachments],确保 ArkUI 响应式列表瞬间重绘
  this.attachmentFiles = attachments ? [...attachments] : [];
  
  // 自动重新建立反向索引图谱与 NPU 特征同步
  const attachmentsDir = VaultManager.getInstance().getAttachmentsDir();
  await BrainImageSearchManager.getInstance().syncAttachmentsFromVault(attachments, attachmentsDir);
}

3. 路由 onHidden 闭环兜底

// Index.ets (NavDestination 路由节点)
} else if (name === 'NoteDetail') {
  NavDestination() {
    NoteDetailView({ filename: param as string })
  }
  .hideTitleBar(true)
  .onHidden(() => {
    // 当退出 Markdown 编辑器返回主界面时,强制触发附件列表同步
    AppStorage.setOrCreate('lastInsertedAttachment', `exited_${Date.now()}`);
  })
}

六、 单手化移动端 UX 改造

为了打造媲美原生 Obsidian 的流畅移动端操作体验,我们对交互界面进行了以下优化:

效果图:单手移动端右下角切态与附件管理

HarmonyOS 7 核心实践:基于 CoreVisionKit 端侧 AI 语义搜图与多模态双链 Vault 架构.jpg

HarmonyOS 7 核心实践:基于 CoreVisionKit 端侧 AI 语义搜图与多模态双链 Vault 架构-1.jpg

  1. 单手底部栏切换模式:移除 Markdown 编辑区顶部的切换按钮,统一在底部导航栏最右下角放置 👁️ 预览 / ✏️ 编辑 悬浮控件,大拇指无需跨越屏幕即可一键切态。
  2. 沙箱附件管理:主界面 attachments/ 列表支持实时物理删除(🗑️),点击直接弹出全局图片预览视图。
  3. 纵向流畅滚动:为树形目录与附件列表配置 .scrollable(ScrollDirection.Vertical).scrollBar(BarState.Auto),解决海量文件场景下的卡顿与遮挡问题。

七、 总结与展望

通过充分利用 HarmonyOS NEXT 的端侧 AI 算力与 ArkUI 响应式开发范式,我们在 AeroPlan 中构建了一套高隐私、高效率、多模态的端侧“第二大脑”知识库。

  • 完全脱网安全:图片特征提取与语义检索全部在鸿蒙 NPU 端侧完成,不消耗流量,无隐私泄漏风险。
  • 无缝多模态双链:打破了传统纯文本 Markdown 与二进制图片附件之间的壁垒。
  • 高响应体验:借助状态监听与路由生命周期协同,提供了极速顺滑的即时反馈。

未来,我们将继续探索 CoreVisionKit 的 OCR 文本识别与 CoreSpeechKit 语音输入在个人知识库领域的深度融合,进一步提升鸿蒙原生应用的多模态生产力!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐