漫画阅读器的离线缓存:数据库、文件落盘与清理策略的取舍
做漫画阅读器这几年,我被问得最多的问题之一是:地铁里没信号,能不能接着看。答案取决于离线缓存做得怎么样。缓存看起来只是"把图片存下来",实际动手才发现,它牵扯到数据库、文件组织、后台下载、存储权限和清理策略,任何一环偷懒,都会在用户最需要的时候掉链子。
先说结论:我最后定下来的结构是三层——元数据进数据库,图片文件落盘,内存只做临时层。三层各干各的活,互不越位。下面按顺序讲。
数据库只存元数据,不存图片
最开始我图省事,把章节信息、阅读进度和图片都往数据库里塞。一话几十张图,一张几百 KB,数据库文件很快膨胀到几百 MB,备份和迁移都变慢。最要命的是数据库损坏一次,整部漫画的进度和缓存一起丢。
后来我把职责切开:数据库只存元数据——漫画 ID、章节 ID、阅读进度、下载状态、更新时间;图片一律以文件形式存在磁盘上,数据库里只记相对路径。这样数据库始终保持在 MB 级,就算某张图片文件损坏,也只影响那一张,重新下载就能修复,不会牵连整库。
选型上我用的是 SQLite 一类轻量嵌入式数据库,对个人开发者来说够用、够稳,跨平台也不用额外操心。关键是表结构要留好扩展位:下载状态至少分"未下载、下载中、已完成、失败"四种,别只用一个布尔值。只用布尔值的话,下载中途断网,重启后分不清"没下过"和"下了一半",只能整章重下。
文件落盘:按章节分目录
图片落盘有两种常见组织方式。一种是按"漫画 / 章节 / 页码"分目录存,另一种是按内容哈希扁平化存在一个目录里,文件名就是哈希值。
哈希扁平化的好处是天然去重,同一张图在不同章节出现只存一份。但它的代价是调试和清理都很痛苦。用户反馈"第 32 话第 5 页打不开",我拿着哈希文件名根本定位不到是哪张图。
我最后选了按章节分目录。目录结构本身就是索引:打开文件夹就知道哪部漫画、哪一话、缺了哪几页,清理时直接删目录,不用逐条查库。去重确实牺牲了,同一张图可能存两份,但漫画场景里跨章节复用的图片本来就少,省下的那点空间不值得把结构搞复杂。
还有一个细节:文件名别直接用源站的原始文件名,有些带特殊字符、超长甚至重名。我现在按页码编号,001.jpg、002.jpg 这样排,页码即顺序,扫目录就能按顺序读,不用再查库排序。
断点续传:下载一半不该从头再来
离线缓存最影响体验的其实是下载过程。用户点"下载整话",切到后台回个消息,回来发现下载停了、进度归零,这种体验一次就够让人卸载。
我踩过的坑有两个。第一是后台下载被系统杀掉。移动端上,应用切后台一段时间后,下载任务随时可能被系统回收。我的做法是把下载状态实时写库:每张图下完立刻把状态从"下载中"改成"已完成",而不是整章下完再一次性提交。这样即使进程被杀,重启后也能从断点继续,只补没下完的那几张。
第二是不要在下载失败时立刻重试。网络抖动时立即重试,等于拿失败去撞失败。我现在用退避重试:第一次失败等 2 秒,第二次 5 秒,第三次 15 秒,三次都失败就标记失败、等用户手动重试。
另外,下载要分 WiFi 和流量两种策略。默认只在 WiFi 下自动缓存用户追更的章节,流量环境下只下用户手动点的那一话。这个默认值看似保守,但它避免了用户在不知情的情况下被吃掉流量。
清理策略:自动清理要有上限,也要有底线
缓存不清理,迟早把用户手机塞满;清理太激进,用户离线时打开发现章节没了,等于缓存白做。我的策略分三档:
第一档是空间上限。给缓存设一个总容量上限,默认 2GB,超过后按"最久未读"淘汰。关键是淘汰粒度:按章节淘汰,不要按单张图片淘汰。删掉一话里的几张图,那一话就看不完整,还不如整话删掉。
第二档是用户手动清理。设置页里放一个"清理缓存"入口,点开先显示当前缓存占了多少空间、分别是哪些漫画占的,让用户自己决定删什么。不要搞"一键清理"还不告诉用户清掉了什么。
第三档是底线:正在追更、且用户最近三天内打开过的漫画,不参与自动淘汰。自动清理的目的是回收冷数据,不是跟用户的阅读习惯作对。这一条我写进代码里当硬规则,而不是可配置项。
跨平台的存储坑:权限与路径各不相同
Venera-Next 是 Flutter 跨平台应用,Android、iOS、Windows、Linux、macOS 都要跑,存储这件事在每个平台上的坑都不一样。
Android 上最大的坑是分区存储。从 Android 10 开始,应用私有目录之外的写入受到严格限制。我的做法是缓存只写应用私有目录,不碰公共目录——不需要额外申请存储权限,随应用卸载自动清理。代价是换手机时缓存不会跟着走,但缓存本来就该是可再生的,丢了重下就行,真正需要跨设备同步的是阅读进度,而不是图片文件。
iOS 上要注意系统清理。iOS 会在存储空间不足时清理应用的缓存目录,而且不会提前打招呼。所以缓存目录只能放"丢了能重下"的东西,阅读进度、书签这些丢不得的数据必须放进文档目录。这个区分如果做反了,用户某天打开应用会发现进度全丢了,而图片还在。
桌面端反而最省心:Windows、Linux、macOS 上路径稳定、权限宽松。唯一要注意的是别把缓存和用户手动导入的本地漫画混在一个目录里。本地漫画是用户的资产,卸载应用也不该删;缓存是临时数据,卸载可以清。两个目录一旦混了,误删用户文件或本地漫画都是不可逆的事故。
结尾:缓存是体验,不是功能清单上的一行
回头看,离线缓存没有银弹。数据库还是文件、哈希还是分目录、自动清理还是手动清理,每一组选择都有代价。我的取舍标准只有一个:用户在没网的时候能不能顺利接着看,看到一半会不会卡住,看完之后手机会不会被塞满。
按这个标准,三层结构、按章节分目录、断点续传加退避重试、三档清理策略,是我目前觉得最稳的一组答案。它背后的思路是通用的:把不可再生的东西(阅读进度、书签)和可再生的东西(图片文件)分开管,对可再生的东西大胆清理,对不可再生的东西死守底线。
如果你也在做类似的离线功能,不妨先问自己三个问题:断网时用户能看到哪一层?进程被杀后下载能从哪续上?空间满了先删谁?这三个问题想清楚了,缓存的大方向就不会错。上面这些做法都来自我维护 Venera-Next 的实际经验,感兴趣可以去仓库里看看实现。
- 点赞
- 收藏
- 关注作者
评论(0)