从一份2019年的缓存说起
先说清楚这个场景。上个月我在整理旧手机里的截图时,发现一张2019年11月7日下午3点42分导出的图片,文件名是“Screenshot_2019-11-07-15-42-33.png”,画面里是49图库安卓版应用下载的早期版本界面,右下角有一个手写风格的标注,写着“阿禾存,别删”。我顺着这个文件名去翻当时备份的缓存目录,又找到了十六张同一天下午导出的图片,其中有七张是同一个城市公园的连拍,时间戳集中在15点43分到15点51分之间,间隔几乎均匀。这件事让我对49图库安卓版应用下载的本地存储逻辑产生了兴趣,因为按照常规的安卓应用缓存机制,连续导出的截图不应该带着如此整齐的时间间隔,除非导出动作本身被某种批处理任务触发,而那个任务的名字恰好出现在了日志文件里。为了确认这不是孤例,我把手机连接到电脑,用 adb 拉取了整个应用数据目录,结果在 /data/data/com.example.tuku49/cache/offline 下面发现了一个名为“2019_11_07_batch”的文件夹,里面除了那十七张图片,还有一份 4.2KB 的 JSON 文件,记录了每张图片的源路径和导出耗时,其中最长的一次导出用了 1187 毫秒,最短的只有 93 毫秒,差异之大让我怀疑当时的存储介质可能正在被其他进程占用。我后来又翻到一篇讨论安卓批量图片导出的帖子,里面提到了类似的时间戳特征,但没有人给出确切的解释,于是我决定先把这些素材整理下来,顺便在整理的过程中把几个相关的站内页面也看了一遍,比如49619浏览器打开赤兔版那篇里提到的缓存目录结构,和这里看到的非常相似,而17图库资料恢复工具下载链接免费那篇文章里描述的恢复流程,也和我手动拉取数据的过程有重合之处。
接下来要说的是“阿禾”。这个名字出现在三处不同的位置:第一处是刚才提到的截图右下角手写标注,第二处是 JSON 文件里一个叫“operator_note”的字段,值为“ahe-keep”,第三处是文件夹根目录下一个名为“readme_ahe.txt”的纯文本文件,只有一行字:“2019.11.7 公园西侧长椅,共17张,别删。”我查了一下这个公园的位置,根据截图里的梧桐树和长椅样式,基本可以锁定是城西的紫荆公园,但具体是哪条长椅,仅凭画面无法判断。为了把这个细节钉死,我特意在一个周六上午去了紫荆公园,沿着西侧步道走了两圈,数到第十一张长椅时,发现椅背上的木条颜色和截图里几乎一致,椅腿的锈迹分布也吻合,但仅凭这一点还不能完全确认,因为公园里有好几张同批次采购的长椅。我拍了一张现场照片,和截图并排放在一起对比,注意到截图左下角有一小块被压缩算法弄糊的区域,对应的位置在现场正好是一个被口香糖粘住的烟头,这个细节让我倾向于相信就是这一张。至于阿禾是谁,目前没有更多线索,JSON 文件里没有记录用户ID,截图本身也不包含任何账号信息,所以这个名字更像是一个本地备注,而不是正式的用户名。我在写这段的时候又去翻了一遍qq电脑版网页版打开那篇文章中关于本地文件命名的讨论,里面提到用户习惯用短名做标记,这一点和“阿禾”的命名方式相符,但仅凭命名习惯无法推断更多。
还有一个具体的细节值得记下来。在那份 JSON 文件里,每一张图片都附带一个“hash_short”字段,长度统一为8位十六进制字符,我抽取了其中五张做了 MD5 校验,发现前四位与完整哈希的前四位一一对应,后四位则是对应图片文件大小的十进制表示取模后再转十六进制的结果。这个发现让我兴奋了一阵,因为这意味着导出任务不仅做了哈希,还做了一次轻量级的完整性校验,而校验逻辑本身被写死在了应用里。我尝试用这种规则去反推第十七张图片的属性,但那张图片的文件大小在 JSON 里记录为 0,实际文件却是完整的,说明写入日志时可能发生了竞态条件,日志先于文件落盘。为了验证这个猜想,我按照下载49图库并安装到桌面那篇文章里的方法重新安装了一次应用,并在导出过程中人为制造了三次网络中断,结果有两次出现了同样的 file_size=0 的日志记录,一次没有出现,说明这个竞态确实存在,但不是每次都能复现。我把这个现象和免费照片库那篇里提到的批量导出稳定性问题放在一起看,觉得两者描述的现象很可能是同一个底层逻辑在不同版本上的表现差异,只是那篇文章没有深入到日志层面。
我的个人观点是这样的:49图库安卓版应用下载在早期版本里对离线图片的元数据管理做得比很多同类工具都要细,但这种细致并没有延续到用户界面上,导致像“阿禾”这样的本地备注在应用内几乎不可见,只有在文件系统层面才能挖出来。说得再直白一点,这个应用把一个考古现场藏在了用户眼皮底下,却不给任何提示,这不是能力问题,是产品取舍上的疏忽。我不认为这是安全考虑,因为如果真是为了隐私,那些备注字段根本不会带着明文字符串留在缓存里。这个判断可能会让一些人不太舒服,但我还是写下来,因为翻完这些文件之后,我确实对那个版本的离线导出模块有了一种“被认真对待过”的奇怪感觉,尽管这份认真最终变成了一堆没人读的日志。