1171 字
6 分钟

重装后收藏/喜欢 歌曲不见了:一次「ID 稳定性 + 数据迁移」的修复记录

重装后收藏/喜欢不见了:一次「ID 稳定性 + 数据迁移」的修复记录#

最近有个很典型、也很容易被忽略的问题:应用重装之后,收藏的歌曲、喜欢的歌曲看起来“全没了”

更准确地说是:收藏数据其实还在本地存储里,但 UI 匹配不到对应的歌曲,于是喜欢列表/歌单展示为空。

现象与日志#

用户侧的表现很直观:

  • 重装 App
  • 重新扫描本地歌曲(或重新授权)
  • 「我喜欢的音乐」变成空
  • 收藏/喜欢按钮状态也全变回未收藏

日志非常“打脸”:一边说 favorites 确实加载出来了,另一边却说匹配结果为 0。

[Favorites] Loaded 17 favorites: {804604524, 749312547, ...}
[Match Debug] First 3 song IDs: [1414957450, 147442901, 1927087886]
[Match Debug] First 3 favorites: [804604524, 749312547, 37290334]
[Match Debug] Matched 0 songs out of 17 favorites

这说明两件事:

  1. favorites 的持久化没坏(能读到 17 个 id)
  2. songs 列表也没坏(扫描出了歌曲 id)
  3. favorites 里存的 id,和当前扫描出来的 song.id 已经不是同一套体系

排查路径:到底是谁变了?#

收藏/喜欢的存储很简单:本地只存 Set<int> 的 songId(shared_preferences),UI 展示时用:

final favoriteSongs = songs.where((s) => favorites.contains(s.id)).toList();

因此只要 s.id 的生成规则发生变化(或同一首歌在重装后拿到的 id 变了),favorites 就会“全部失效”。

我把排查重点放到两类常见不稳定来源:

1)系统媒体库 id 不稳定#

Android/iOS 的系统媒体库可能返回一个“看起来像主键”的 id(例如 on_audio_querySongModel.id),但它未必承诺跨重装、跨版本、跨扫描一致。

2)用「文件绝对路径」生成 id,会被 iOS 重装打爆#

iOS 卸载重装后,App 的沙盒容器路径会变(例如 .../Application/<UUID>/Documents/... 里的 <UUID> 变了)。

如果 id 是 hash(绝对路径),那同一个文件在新容器里就会产生完全不同的 id。

更糟的是:我之前为了修别的问题,把 id 从一种 hash 改成了另一种(比如 String.hashCode vs 自定义 hash),这也会让旧数据瞬间“断链”。

定位到关键点:收藏/歌单依赖「稳定 songId」#

播放器里有三处会依赖 songId:

  • 喜欢/收藏(favorite_songs
  • 本地歌单(歌单里存 songIds)
  • 播放状态恢复(缓存 playlist songIds + index)

只要 songId 不稳定,这三个功能都会在“升级/重装/换机”时出现类似问题。

所以修复目标不是“让匹配代码更聪明”,而是:

  1. 定义一套跨重装稳定的 songId 规则
  2. 对历史版本产生的旧 id 做迁移

解决方案一:canonical path + 稳定 hash#

我新增了一个 SongId 工具(lib/core/services/song_id.dart),做两件事:

  1. 对文件路径做 canonicalize:
    • 如果文件位于 app 的 Documents 下,把“安装相关”的前缀剥离掉
    • 把路径变成 "<DOCS>/Music/xxx.flac" 这种相对且稳定的形式
  2. 对 canonical path 做确定性 hash(djb2,并限定 31-bit 正整数)

核心思路:同一首歌只要相对路径不变,重装后 id 也不变

解决方案二:启动时自动迁移旧数据#

光有新规则还不够:用户重装/升级后,preferences 里存的还是旧 id。

因此我在“扫描歌曲完成后”(LocalSongsNotifier.scanSongs())追加了迁移步骤(lib/core/services/providers.dart):

  1. 用当前扫描到的 songs 建一张映射表:
    • key:旧算法可能生成的 legacyId(例如 filePath.hashCode、旧的 hash)
    • value:当前 canonicalId(新规则算出来的 song.id
  2. 用这张表批量改写:
    • favorites(FavoritesService.migrateSongIds
    • playlists(PlaylistService.migrateSongIds,并保持原顺序去重)

迁移策略里有个小细节:如果同一个 legacyId 映射到多个 canonicalId(冲突),我会丢弃这个 legacyId 的映射,避免误把 A 歌迁到 B 歌。

验证与结果#

我补了一个最小单测,专门验证“iOS 容器路径变了,id 仍然一致”(test/song_id_test.dart):

  • .../Application/AAA/Documents/Music/foo.mp3
  • .../Application/BBB/Documents/Music/foo.mp3

canonicalize 后都应变成 "<DOCS>/Music/foo.mp3",因此 id 相同。

最终效果:

  • 重装/升级后,favorites 仍然能匹配到歌曲
  • 喜欢列表不再空
  • 本地歌单不会因为 id 体系变化而“全失效”

小结#

这类问题的根本原因通常不是“收藏没存上”,而是 你存的是一个不稳定的引用(songId)

经验总结:

  • 只要你把业务数据(收藏/歌单/播放缓存)建立在某个 id 上,就要把这个 id 当作“长期协议”来设计
  • iOS 沙盒路径在重装后会变,绝对路径直接参与 id 计算会天然不稳定
  • 一旦你不得不改 id 规则,务必提供迁移,否则用户数据会在升级后“看起来丢了”

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
重装后收藏/喜欢 歌曲不见了:一次「ID 稳定性 + 数据迁移」的修复记录
https://www.ymxx.net/posts/favorites-reinstall-fix/
作者
Leguan
发布于
2026-01-16
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Leguan
Hello, I'm Leguan.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
站点统计
文章
9
说说
10
分类
3
标签
17
总字数
10,655
运行时长
0
最后活动
0 天前

目录