Android学习总结之Glide自定义三级缓存(面试篇)

一、三级缓存核心原理与设计
问题 1:为什么需要三级缓存?各层缓存的核心作用是什么?

回答核心

  • 内存缓存:毫秒级快速响应,存储近期浏览的图片(如滑动列表来回切换的图片),通过 LRU 算法自动清理冷数据,通常占内存 15%。
  • 磁盘缓存:持久化存储常用图片,解决内存容量限制,支持离线访问(如商品详情页图片),按 URL 哈希值命名避免重复,容量 100MB 左右。
  • 网络缓存:结合 HTTP 缓存头(如 Cache-Control),避免重复下载,降低流量和服务器压力,容量 50MB,优先存高频访问图片。
  • 分层优势:形成 "速度优先→持久化存储→网络优化" 的三级防护,覆盖 90% 以上的图片加载场景,提升用户体验和系统稳定性。

话术示例

三级缓存的设计是为了解决图片加载中的三个核心问题:

  • 内存缓存(快):就像手机的 "最近使用列表",存用户刚看过的图片(比如滑动列表时来回切换的图片)。用 LRU 算法自动清理太久没看的图片,比如 8GB 内存的手机分 1.2GB 给它,还会把大图片压缩到屏幕大小,确保滑动时瞬间加载,不卡顿。
  • 磁盘缓存(稳):相当于 "本地相册",存常用但暂时不在内存里的图片(比如用户常看的商品详情页图)。存 100MB 左右,用 URL 的哈希值命名避免重复,即使手机重启或没网,也能从这里找到图片。
  • 网络缓存(省) :类似 "路由器记忆",避免重复下载。比如用 OkHttp 缓存 50MB,配合服务器的缓存策略(比如设置 1 天有效期),下次打开同一张图直接从路由器拿,省流量还减轻服务器压力。
    三层配合,比如用户滑动列表时,先从内存秒开,滑走后存磁盘,下次没网也能看,联网时优先用网络缓存避免重复下载,覆盖 90% 的使用场景。
问题 2:LruCache 算法的核心数据结构和工作机制是什么?

回答核心

  • 数据结构:双向链表(维护访问顺序)+ 哈希表(快速查找)。
  • 工作机制
    1. 新元素加入链表头部,存在元素访问后移到头部;
    2. 缓存满时删除链表尾部元素(最久未使用);
    3. 通过sizeOf()方法计算元素大小,支持自定义内存占用(如 Bitmap 的字节数)。
  • 大厂优化点:Glide 的 LruResourceCache 结合弱引用队列,感知 Bitmap 回收,避免内存泄漏,同时复用资源池对象减少创建开销。

话术示例

LruCache 就像一个 "智能书架",核心是按 "最近最少用" 原则管理图片:

  • 每次取图片(访问),就把它放到书架最前面(双向链表头部),很久没取的书(最久未用)放在最后面(链表尾部);
  • 书架满了(内存不够),直接扔掉最后一本书(删除尾部元素),保证书架始终不超载。
    Glide 在这个基础上做了两个关键优化:
  1. 防内存泄漏:给图片加 "弱引用",就像给书贴一个标签,书被卖掉(系统回收)时,标签自动从书架移除,避免书架上留空标签(无效引用);
  2. 资源复用:把用过的图片暂时存到 "回收箱"(资源池),下次需要相同尺寸的图片,直接从回收箱拿,不用重新买(创建 Bitmap),比如滑动列表时,同尺寸的图片反复用,内存占用能降 30% 以上。
二、常见问题解决方案(缓存穿透 / 雪崩 / OOM)
问题 1:缓存穿透的本质是什么?大厂如何解决高并发下的穿透问题?

回答核心

  • 本质:请求不存在的数据,导致每次都查数据库 / 网络,形成流量黑洞。
  • 解决方案
    1. 布隆过滤器:提前将所有合法 URL 存入布隆过滤器,请求时先过滤无效 URL,拒绝率达 99% 以上;
    2. 缓存空值:对不存在的 URL 缓存一个特殊值(如 NULL),设置短过期时间(5 分钟),防止恶意攻击;
    3. 占位图策略 :Glide 中设置error()/fallback()占位图,避免界面闪烁,同时记录无效请求日志。
问题 2:缓存雪崩的危害及多级预防策略?

回答核心

  • 危害:大量缓存同时失效,瞬间流量冲击数据库,可能导致服务雪崩。
  • 预防策略
    1. 错峰过期:给缓存时间添加随机偏移(如 24 小时 ±1 小时),避免集中失效;
    2. 多级缓存:内存 + 磁盘 + CDN 三层缓存,CDN 层抗住 80% 静态资源请求;
    3. 熔断降级:流量突增时,返回低清图或占位图,保证核心流程可用;
    4. 互斥锁:缓存失效时,仅允许一个线程重建缓存,其他线程阻塞等待,避免并发查库。

话术示例

缓存穿透是 "无效请求攻击",比如恶意用户大量请求不存在的图片 URL,导致每次都要查数据库,就像有人一直按门铃问 "10086 号房在吗",但小区根本没这个房号。

大厂用三层防线解决:

  1. 门口装 "门禁"(布隆过滤器):提前把所有存在的房号(URL)录入门禁系统,访客(请求)先刷门禁,不存在的直接拦在门外,准确率 99% 以上,比如电商 APP 用布隆过滤器,每天能拦截 10 万 + 无效请求;
  2. 留 "空房记录"(空值缓存):对查过不存在的房号,记下来 "10086 号房不存在",有效期 5 分钟,期间再有人问直接说 "不用查了",防止短时间内重复攻击;
  3. 门口贴 "提示牌"(占位图):在 Glide 里设置默认图,比如请求失败时显示 "图片加载中" 的占位图,用户看不到空白,体验更好,同时后台记录这些无效请求,方便定位攻击源。
问题 3:如何从源头预防 OOM?Glide 中的关键配置有哪些?

回答核心

  • 内存优化三原则
    1. 尺寸压缩 :按 ImageView 尺寸加载(override(width, height)),避免加载超分辨率图片;
    2. 格式转换 :使用 RGB_565(比 ARGB_8888 节省 50% 内存)或 WebP 格式,Glide 中通过format(DecodeFormat.PREFER_RGB_565)配置;
    3. 生命周期绑定 :Glide 自动与 Activity/Fragment 绑定,界面不可见时清理资源,避免内存泄漏,同时可手动调用clear(imageView)释放。
  • 进阶策略 :动态调整内存缓存大小(如低端机设为 10%,高端机 20%),结合skipMemoryCache(true)跳过非当前屏幕图片的内存存储。

话术示例

OOM 就像 "书包塞满了大书",图片太大或存太多就会撑爆内存。Glide 通过三个 "瘦身" 技巧预防:

  1. 按尺寸买书 :比如手机屏幕是 1000px,就只加载 1000px 的图,不加载 2000px 的原图,用override(1000, 1000)强制压缩,内存占用直接减半;

  2. 选轻便包装 :用 RGB_565 格式代替默认的 ARGB_8888,前者每个像素占 2 字节,后者占 4 字节,同样一张图,内存占用少一半,配置代码:

    复制代码
    Glide.with(context).load(url).format(DecodeFormat.PREFER_RGB_565);  
  3. 定期断舍离 :Glide 会自动跟着 Activity/Fragment 的生命周期清理内存,比如页面关掉时,把相关图片从内存删掉,也可以手动调用clear(imageView),避免 "过期图片" 占空间。
    比如一个短视频 APP,通过这三个技巧,内存峰值能从 500MB 降到 300MB 以下,OOM 崩溃率下降 70%。

话术示例

缓存雪崩就像 "电梯超载",比如双十一零点,大量商品图片的缓存同时过期,几十万人同时请求,数据库像电梯一样可能被挤瘫。

大厂在大促时会做三件事:

  1. 错峰下班 :给每个缓存设置不同的过期时间,比如原定 24 点过期,让有的 23 点 50 分过期,有的 0 点 10 分过期,像员工分批次下班,避免电梯拥挤。代码上可以加随机值:

    复制代码
    int expireTime = 24*60*60 + new Random().nextInt(3600); // 过期时间波动±1小时  
  2. 多级防护:最外层用 CDN 缓存(比如阿里云 OSS),扛住 80% 的图片请求,中间层用本地磁盘缓存,最后才到数据库,就像 "保安 + 前台 + 门禁" 三层过滤;

  3. 降级处理:如果流量实在太大,暂时给用户看低清图或模糊图,比如把 10MB 的高清图换成 1MB 的低清图,保证页面能加载,同时对数据库访问加锁,同一时间只允许一个线程更新缓存,其他线程等待,避免所有人同时挤向数据库。

三、HTTP 缓存与网络优化
问题 1:Cache-Control 头中 max-age、no-cache、no-store 的区别和使用场景?

回答核心

  • max-age=3600:缓存有效期 1 小时,期间直接读本地缓存,适合不常更新的图片(如商品主图);
  • no-cache:每次请求需服务器验证缓存有效性(发 304 请求),适合频繁更新但需浏览器缓存的图片(如活动海报);
  • no-store:禁止任何形式的缓存,响应内容不落地,适用于敏感图片(如用户证件照)。
  • 最佳实践:同时配置 ETag 和 Last-Modified,ETag 做精准校验(解决时间戳精度问题),Last-Modified 做快速判断,提升 304 命中率。
问题 2:客户端如何强制获取最新图片?服务器端如何配合?

回答核心

  • 客户端方案
    1. URL 添加版本号或时间戳(如image.jpg?v=2),破坏缓存键一致性;
    2. 设置请求头Cache-Control: no-cache,强制服务器验证;
    3. 清除本地网络缓存(OkHttp 中通过cache.remove(request))。
  • 服务器端配合
    1. 返回正确的 Cache-Control 头(如更新时设置max-age=0);
    2. 图片变更时更新 ETag 值,确保客户端能检测到变化。
四、性能监控与架构设计
问题 1:如何计算缓存命中率?大厂关注哪些核心指标?

回答核心

  • 计算公式:缓存命中率 =(内存命中数 + 磁盘命中数)÷ 总请求数 × 100%。
  • 监控手段
    1. Glide 开启 DEBUG 日志,筛选Fetched from memory cacheFetched from disk cache条目;
    2. 自定义 ModelLoader 统计各层命中次数;
    3. 关注衍生指标:内存峰值(Android Profiler 监控 Heap Size)、加载耗时(System.currentTimeMillis () 打点)、FPS(确保滑动≥55fps)。
  • 优化目标:内存命中率≥30%,磁盘命中率≥50%,整体命中率≥80%。
问题 2:设计一个高并发图片缓存系统,需要规避哪些坑?

回答核心

  • 核心坑点与对策
    1. 热点问题:高频图片集中失效,通过 "热点缓存 + 本地副本" 解决(如 Redis 热 key + 本地 Ehcache);
    2. 存储碎片化:URL 哈希冲突(概率极低),通过加盐哈希或 SHA-256 提升唯一性;
    3. 跨平台一致性:多端(Android/iOS/Web)缓存策略统一,如使用相同的 URL 参数规则和 Cache-Control 配置;
    4. 容量失控:磁盘缓存设置严格的 LRU 淘汰策略 + 过期时间(如 7 天未访问则删除),定期清理僵尸文件。
相关推荐
后端码匠2 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
令狐前生3 小时前
设计模式学习整理
学习·设计模式
湘-枫叶情缘3 小时前
解构认知边界:论万能方法的本体论批判与方法论重构——基于跨学科视阈的哲学-科学辩证
科技·学习·重构·生活·学习方法
梓仁沐白3 小时前
Android清单文件
android
inputA4 小时前
【LwIP源码学习6】UDP部分源码分析
c语言·stm32·单片机·嵌入式硬件·网络协议·学习·udp
海尔辛4 小时前
学习黑客5 分钟读懂Linux Permissions 101
linux·学习·安全
董可伦6 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
真的想上岸啊6 小时前
学习51单片机01(安装开发环境)
嵌入式硬件·学习·51单片机
恋猫de小郭7 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin