创新项目实训博客(五):Flutter 端侧回忆推荐引擎设计与状态复用实践
这份博客主要记录了本周进行的智能影记个性化推荐功能的添加过程和代码逻辑
文章目录
项目名称:智能影记 - Memoria
时间:2026.04.06 - 2026.04.12
核心任务:首页“发现”模块重构、端侧三级推荐策略落地、虚拟事件流架构设计
一、 前言:赋予本地相册“主动感知”的能力
在前面的实训中,我们完成了 Isar 高性能数据库的接入和相册元数据(时间、地点、标签)的落盘。但一个优秀的相册 App 不能仅仅是一个“静态的数据仓库”,它应该像一个懂你的老朋友,能在适当的时候把遗忘的美好主动推送到你面前。
本周我的核心任务,就是重构 HomePage 的“发现”模块,在完全不依赖云端算力、绝对保护隐私的前提下,利用端侧的元数据矩阵,纯本地实现一套“智能回忆推荐引擎”。
二、 业务逻辑:端侧三级渐进式推荐策略
受限于移动端的算力和电池续航,我们不能在每次打开首页时遍历数万张照片跑复杂的推荐算法。因此,我设计了一套基于 Isar 极速查询的“三级渐进式规则引擎”,按照优先级依次生成推荐卡片(最多展示两张,以保证首屏加载速度):
规则 1:时间感知策略 (Time-based)
系统会动态获取当前的 DateTime.now(),通过业务边界条件判断推送逻辑:
- 年度/月度总结:当日期处于 12.20 - 1.10 时,触发“年度大片”;当每月日期大于 25 号时,触发“月度回顾”。
- 往年今日:通过一个
for循环,向前追溯 1 到 5 年的今天。利用 Isar 的timestampBetween索引进行毫秒级过滤。
规则 2:内容画像策略 (Content-based)
如果时间策略未完全填满推荐位,系统会提取近期照片的 aiTags 进行画像分析。
我将标签划分为了四个高优分类:宠物 (pets)、风景 (scenery)、美食 (foods) 和 治愈系 (happy)(结合 AI 提取的 joyScore > 0.6)。如果某一类的照片积攒到了一定阈值(如美食 >= 6 张),就会动态生成对应的聚类卡片。
规则 3:地点保底策略 (Location-based)
最后,通过提取照片的 city 或 province 字段,将同一个地点拍摄的大量照片打包成“城市印记(如:青岛·漫游记)”作为推荐的保底策略。
三、 工程挑战与解决方案
在具体编码落地的过程中,我遇到了两个典型的移动端工程挑战,并针对性地给出了解决方案。
3.1 挑战一:缓存路径失效与异步状态刷新
在移动端(特别是 Android),照片的绝对沙盒路径是可能会因为系统清理缓存而发生变动的。如果在展示推荐卡片时直接读取失效的 Path,会导致 UI 渲染大面积黑屏。
解决方案:
我在 home_page.dart 的 _generateDiscoverCards 阶段,引入了 PhotoService().reconcileAccessiblePhotos 机制。在照片喂给 UI 渲染前,先通过永远不变的 assetId 去系统底层换取最新有效路径,同时过滤掉已经被用户在系统相册中物理删除的照片。
配合 Flutter 的 setState,确保了数据清洗和 UI 状态的完美同步。
3.2 挑战二:页面跳转的“数据孤岛”问题
点击推荐卡片后,需要跳转到“事件详情页”查看照片瀑布流并支持“一键生成视频”。但推荐出来的照片集合(比如“我的美食日记”)在底层数据库中并没有真实对应的 EventEntity(事件实体),它们只是一组临时的查询结果集。如果为此单独写一个详情页,会导致严重的代码冗余。
解决方案:虚拟事件模式
采用了经典的 DTO(数据传输对象)设计模式。我在点击事件的 onTap 回调中,利用内存里的查询结果,动态构造了一个 id = '-1' 的虚拟 Event 对象,并为其注入了虚拟的 AI 主题:
// 截取自 home_page.dart 推荐卡片点击逻辑
onTap: () async {
// ... 前置的路径清洗逻辑
// 1. 构造穿透底层的虚拟 Event (内存对象,不写数据库)
final virtualEvent = Event(
id: '-1', // 标识为虚拟事件
title: cardData['title'],
season: '智能推荐',
year: sortedDates.first.year,
location: '精选回忆',
startDate: sortedDates.first,
endDate: sortedDates.last,
photos: mappedPhotos,
aiThemes: [
AITheme(
id: 'discover_theme',
emoji: '✨',
title: cardData['title'],
subtitle: cardData['tag'],
)
],
);
// 2. 完美复用现有的 EventDetailPage,实现视图层解耦
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EventDetailPage(event: virtualEvent),
),
);
}
通过这种“内存数据伪装”的架构设计,我成功实现了推荐流和原生事件流在 UI 层的完美闭环,复用了全部的下游业务逻辑。
四、 本周总结与下周计划
本周通过对 HomePage 的重构,Memoria 在不需要任何云端推荐算法干预的情况下,拥有了一套“千人千面”的端侧本地推荐引擎。这不仅贯彻了项目的隐私保护初衷,也大幅提升了产品的使用温度。
目前,推荐卡片点击进去后的“一键生成故事”功能已经跑通了基于文本的 LLM 生成。下周,项目将迎来最激动人心的架构换血:纯端侧的 C++ 向量引擎即将并网。 我将配合底层同学,把目前基于“时间与文本标签”的检索彻底替换为“多模态 Embedding 余弦搜索”。
前端的状态机已经搭建完毕,期待下周底层算力的全面注入!
更多推荐
所有评论(0)