第86篇 | HarmonyOS 去年今日和附近记忆:照片记录如何触发故地重游
第86篇 | HarmonyOS 去年今日和附近记忆:照片记录如何触发故地重游
第86篇讲“故地重游”。双镜记忆相机不是只把照片放进相册,还会在用户回到曾经拍摄的位置附近时,把相关回忆卡片提示出来。这个体验由地图记忆点、距离计算、周年记录匹配和通知触发共同完成。
这一篇重点看 anniversaryRecallRecordId、anniversaryRecallMemoryId 和 updateAnniversaryRecall。它们把“附近位置”和“过去照片”连接起来,让照片记录重新变成当前场景里的提示。
本篇目标
- 理解附近记忆为什么需要位置距离和时间线共同判断。
- 掌握 anniversaryRecallRecordId 和 memoryId 如何组成回忆 key。
- 看清回忆卡片如何打开相册指定记忆。
- 理解回忆提醒和通知触发之间的关系。
对应源码位置
superImage/entry/src/main/ets/pages/Index.etssuperImage/entry/src/main/ets/services/GalleryRecordService.ets
照片记录可以重新唤起场景
运行路径图里,用户不是主动搜索旧照片,而是在地图场景里接近曾经拍摄的位置。应用把位置距离、记忆点和相册记录组合起来,提示“你在这里记录过”。
这个体验比普通相册列表更有作品感。它把照片从静态记录变成当前位置的上下文,也给通知回流和 LiveView 留下入口。

地图页根据当前位置和历史照片记录触发故地重游卡片
状态字段保存当前命中的回忆
anniversaryRecallRecordId 和 anniversaryRecallMemoryId 分别保存照片记录和地图记忆点。两者合在一起才是一次完整的回忆命中。
这种设计避免只用 recordId 或只用 memoryId 造成歧义。用户可能在同一地点拍过多组照片,也可能多个记忆点关联相似位置,组合 key 更稳。

页面用 recordId、memoryId 和提示文案保存当前命中的回忆
@State private videoPreviewFrameIndex: number = 0;
@State private galleryNoticeText: string = '照片会自动进入相册';
@State private anniversaryRecallRecordId: string = '';
@State private anniversaryRecallMemoryId: string = '';
@State private anniversaryRecallText: string = '回到曾经拍摄的位置附近时,这里会自动提醒相关回忆';
@State private dismissedAnniversaryRecallKey: string = '';
@State private vaultUnlocked: boolean = false;
@State private vaultAuthBusy: boolean = false;
@State private vaultSelectedId: string = '';
公开记录参与匹配
回忆匹配应该只看公开相册记录,保险箱照片不能因为用户接近某个地点就自动暴露。getAnniversaryRecallRecord 从公开记录中找当前命中的 recordId,保证边界清楚。
这也是隐私体验的一部分。故地重游很温柔,但前提是它不会把私密照片变成主动提醒。

回忆卡片通过公开相册记录和组合 key 确定当前提醒
if (this.anniversaryRecallRecordId.length === 0) {
return undefined;
}
return this.getPublicGalleryRecords().find((record: GalleryMoment) => record.id === this.anniversaryRecallRecordId);
}
private getAnniversaryRecallKey(): string {
if (this.anniversaryRecallRecordId.length === 0 || this.anniversaryRecallMemoryId.length === 0) {
return '';
}
return `${this.anniversaryRecallMemoryId}_${this.anniversaryRecallRecordId}`;
}
private isAnniversaryRecallDismissed(): boolean {
const recallKey = this.getAnniversaryRecallKey();
return recallKey.length > 0 && recallKey === this.dismissedAnniversaryRecallKey;
}
更新回忆时同时触发通知
updateAnniversaryRecall 先判断距离是否超过阈值,再找到符合条件的历史记录。命中后,它会更新 recordId、memoryId 和提示文案,并在发生变化时触发场景回忆通知。
注意这里的 changed 判断。只有当前命中的回忆变化,才需要发布通知,避免用户停留在同一地点时不断被重复打扰。

updateAnniversaryRecall 匹配距离、更新文案,并在变化时发布通知
private updateAnniversaryRecall(memory: MemorySpot, distanceMeters: number): void {
if (distanceMeters > this.anniversaryMatchDistanceMeters) {
this.clearAnniversaryRecall();
return;
}
const anniversaryRecord = this.getAnniversaryRecordForMemory(memory);
if (!anniversaryRecord) {
this.clearAnniversaryRecall();
return;
}
const recallTime = this.formatVisitRecallTime(anniversaryRecord.createdAt);
const distanceText = this.formatDistance(distanceMeters);
const nextText = `你在 ${recallTime} 记录过这里,现在距离约 ${distanceText},可以打开当时的照片继续回看那一刻。`;
const changed = this.anniversaryRecallRecordId !== anniversaryRecord.id ||
this.anniversaryRecallMemoryId !== memory.id;
this.anniversaryRecallRecordId = anniversaryRecord.id;
this.anniversaryRecallMemoryId = memory.id;
this.anniversaryRecallText = nextText;
if (changed) {
this.galleryFocusMemoryId = memory.id;
this.gallerySelectedId = anniversaryRecord.id;
this.galleryNoticeText = nextText;
void this.publishSceneRecallNotification(memory, anniversaryRecord, nextText);
}
}
回忆卡片打开相册指定记忆
openAnniversaryRecallInGallery 会把 tab 切到 gallery,设置 focus memory id,并把提示文案写到相册 notice。用户从地图看到回忆后,可以自然回到相册查看当时照片。
这就是“故地重游”的闭环:地图负责触发,相册负责承接,照片记录负责展示内容。不要让提醒只停留在卡片上,要给用户一个明确的下一步。

回忆卡片点击后切换到相册并聚焦对应记忆
private openAnniversaryRecallInGallery(): void {
const anniversaryRecord = this.getAnniversaryRecallRecord();
if (!anniversaryRecord) {
return;
}
this.galleryDetailReturnTarget = '';
this.gallerySelectedId = anniversaryRecord.id;
this.selectedGalleryGroupKey = this.buildGalleryRecordGroupKey(anniversaryRecord);
this.galleryUserNoteDraft = this.getRecordUserNote(anniversaryRecord);
if (this.anniversaryRecallMemoryId.length > 0) {
this.galleryFocusMemoryId = this.anniversaryRecallMemoryId;
}
this.galleryNoticeText = this.anniversaryRecallText;
this.galleryMediaTab = 'photo';
this.resetGalleryDetailViewer();
this.galleryViewMode = 'detail';
void this.startGalleryAntiPeepProtection();
this.switchTab('gallery');
隐私边界和回忆触发验证
故地重游看起来是推荐体验,本质上也涉及隐私边界。通知和页面卡片只应该使用公开相册里的地点、时间和脱敏标题,不能把保险箱照片、隐藏视频或完整文件路径带到通知栏。这样即使通知被截屏,用户也不会暴露敏感内容。
| 数据来源 | 是否参与回忆提醒 | 原因 |
|---|---|---|
| 公开相册记录 | 可以参与 | 用户已经在普通相册中可见 |
| 保险箱记录 | 不参与通知栏提醒 | 通知栏不可控,可能被旁人看到 |
| 无定位记录 | 不参与附近触发 | 无法计算距离,避免误提醒 |
| 已删除记录 | 不参与 | 防止旧快照复活已删除内容 |
回归测试要覆盖时间和空间两个维度:同一天历史记录、附近距离命中、无定位记录、保险箱记录。命中后再检查通知文案和页面卡片是否只包含安全字段。这样文章不仅说明“怎么触发”,也说明“哪些内容不能触发”。
实现上可以把筛选条件放在回忆匹配之前,而不是发布通知时才临时判断。先过滤敏感数据,再计算距离和周年匹配,能降低遗漏风险,也让后续调试更清楚:没有提醒是因为不满足条件,而不是通知模块失败。
工程检查清单
- 回忆提醒只使用公开相册记录,不能暴露保险箱内容。
- recordId 和 memoryId 组合成回忆 key,避免重复或误命中。
- 距离超过阈值时要清理回忆状态。
- 提醒必须能回流到相册指定记忆,而不是只显示一段文案。
今日练习
- 把
anniversaryMatchDistanceMeters调小,观察回忆触发频率变化。 - 模拟同一地点多条记录,思考 recordId 和 memoryId 如何避免歧义。
- 把回忆卡片点击链路画成“地图 -> 相册 -> 记录”的流程图。
完成练习后,建议把“看到的页面结果”和“源码里的状态字段”写在同一张小表里。这个习惯会让后续排查同步、通知、权限和多设备体验时更快定位问题,也能让文章从概念讲解变成真正可复现的工程笔记。
更多推荐


所有评论(0)