缓存基础概念
说到离线缓存,很多新手朋友第一反应是“存点数据到本地呗”。没错,但远不止这么简单。我做了这么多年Android,可以负责任地告诉你——缓存设计的好坏,直接决定了你的App能不能在弱网下活下来。
一句话总结:离线缓存不是“存数据”,而是“让数据在无网状态下依然可用且一致”。
为什么需要离线缓存?
你想想看,用户在地铁里刷App,信号断断续续。如果每次都要等网络请求,体验会怎样?我曾经在做一个新闻客户端时,用户在地库加载了5秒还出不来,流失率直接飙升。从那以后,我再也不敢轻视离线缓存。
离线缓存的核心价值有三个:
- 提升用户体验:数据秒开,不依赖网络
- 减少流量消耗:避免重复请求相同数据
- 保障基础可用:无网络时依然能浏览历史内容
说白了,离线缓存就是把“实时数据”和“本地数据”的差异抹平。用户看到的永远是最新可用的内容,而不是白屏或loading转圈。
缓存层级概述
缓存不是孤立的,它分三个层级——内存、磁盘、网络。我个人习惯把它们想象成一个漏斗:最快的在最上面,容量最小的也在最上面。
| 层级 | 速度 | 容量 | 持久性 | 典型实现 |
|---|---|---|---|---|
| 内存缓存 | 纳秒级 | 几MB~几十MB | 进程退出即清除 | LruCache、HashMap |
| 磁盘缓存 | 毫秒级 | 几百MB~几GB | App卸载才清除 | Room、SharedPreferences、文件 |
| 网络缓存 | 秒级~分钟级 | 取决于CDN | 服务器控制过期 | HTTP Cache、Retrofit缓存 |
这里要注意:内存缓存最快,但最脆弱。我记得有一次,团队把用户配置全放在内存里,一按Home键再回来,数据全丢了,导致页面闪一下空白。嗯,这个坑我踩过。
避坑指南:千万不要把需要持久化的数据只放在内存缓存中。磁盘缓存才是你的“保险柜”。
那么这三个层级怎么配合呢?看下面这张图:
这张图展示了我最推荐的策略——三级缓存逐级回退。先用内存,没命中用磁盘,再没命中走网络。网络返回的数据,再统一更新到磁盘和内存。这样下次请求就能直接命中。
缓存一致性基本问题
好,现在我们有了三层缓存,问题来了:数据不一致怎么办?
举个真实案例。我参与过一个电商项目,用户点赞操作先写到了本地内存,显示已点赞。但实际网络请求失败了,服务器那边根本没记录。这时候用户看到的是“已点赞”,后台却是“未点赞”,这就是典型的缓存与真实数据不一致。
缓存一致性,说白了就是保证用户在任何地方看到的都是同一份“真相”。主要有三种场景:
| 场景 | 问题描述 | 常见解决方法 |
|---|---|---|
| 写后读不一致 | 本地修改后,网络写入慢,其他地方读到旧数据 | 先写本地,异步写网络;或者写时直接过期缓存 |
| 多端冲突 | 手机和服务器数据因同时修改产生冲突 | 时间戳对比、最后写入覆盖、冲突提示让用户选择 |
| 缓存过期 | 在缓存有效期内,服务器数据已变 | 设置TTL(过期时间)、主动推送失效通知 |
我的个人习惯:对于写操作,我倾向于采用“读时校验”策略——读取数据时检查本地时间戳与服务器时间戳是否一致,不一致则丢弃缓存重新拉取。代价是读操作多一次对比,但能有效避免脏数据。
你可能会问:数据不一致能完全避免吗?我的答案是:不能。分布式系统下,一致性只能“尽可能”保证。我们要做的就是让不一致的时间窗口尽量短,让用户基本感知不到。
举个具体的做法:结合Room数据库的版本号机制。每条记录都带一个version字段,每次更新时version加1。读取时对比本地version与服务器返回的version,不一致就覆盖。嗯,这个方法我在好几个项目里用过,效果不错。