完整流程梳理
执行时间:
text
Glide.with(this)
↓
【with():绑定生命周期,获取 RequestManager】
根据 Activity / Fragment / Context
获取对应的 RequestManager,
让图片请求能够跟随页面生命周期自动暂停、恢复和取消。
↓
获取 RequestManager
↓
load(url)
↓
【load():构建请求,记录数据源,并根据数据类型确定对应的 ModelLoader】
↓
主要是构建请求、确定 ModelLoader、记录数据源
↓
into(imageView)
↓
【into():绑定目标 ImageView,并真正启动图片加载请求】
↓
真正开始执行请求
↓
Engine.load()
↓
生成缓存 Key
↓
查内存缓存
↓
内存没有
↓
开启 DecodeJob
↓
查磁盘缓存
↓
磁盘没有
↓
网络加载
第1步:生成缓存Key(请求发起时)
- 执行位置 :
Engine.load() - 核心逻辑 :Glide并不会只拿图片URL做Key。它会将
URL(id)、签名(signature)、宽高(width/height)、解码器、转换器等10多个参数 共同构建成一个唯一的EngineKey对象。 - 目的:确保不同尺寸、不同变换效果的同一张网络图,在缓存中被视为不同的资源。
第2步:读取内存缓存(LruCache算法区)
- 执行位置 :
Engine.load()->loadFromCache() - 核心逻辑 :拿着第1步生成的Key,去 LruResourceCache(基于LruCache算法)中查找。
- 结果处理 :
- 命中 :直接将图片回调给
ImageView并显示(结束流程)。 - 未命中 :继续下一步。
(注意:从LruCache读取时,数据会被暂时移除)
- 命中 :直接将图片回调给
第3步:读取内存缓存(弱引用区)
- 执行位置 :
Engine.load()->loadFromActiveResources() - 核心逻辑 :若LruCache没有,则去 activeResources (弱引用
HashMap)中查找。这块区域专门存放正在被View使用的图片,防止被Lru算法回收。 - 结果处理 :
- 命中:直接回调显示。
- 未命中:开启新的加载线程(进入第4步)。
第4步:启动子线程,优先读取磁盘缓存
- 执行位置 :
EngineRunnable.run()->decode() - 核心逻辑:子线程启动后,首先判断是否允许读取磁盘缓存。默认优先尝试从磁盘读取(因为网络最慢)。
第5步:读取磁盘缓存(先读转换后图片)
- 执行位置 :
decodeFromCache()->decodeResultFromCache() - 核心逻辑 :使用完整的EngineKey (含宽高等参数),从Glide自定义的
DiskLruCache中查找转换后的图片(即压缩/裁剪适配View后的图)。 - 适用策略 :
DiskCacheStrategy.RESULT或ALL模式下生效。
第6步:读取磁盘缓存(后读原始图片)
- 执行位置 :
decodeFromCache()->decodeSourceFromCache() - 核心逻辑 :若上一步没找到,则使用简化Key (仅含URL和signature,忽略宽高等参数),查找原始尺寸的图片。
- 适用策略 :
DiskCacheStrategy.SOURCE或ALL模式下生效。 - 结果处理 :
- 磁盘命中:解码并返回图片,跳至第9步(写入内存缓存并显示)。
- 磁盘未命中:进入第7步(网络加载)。
第7步:从网络/源头获取图片资源
- 执行位置 :
decodeFromSource()->fetcher.loadData() - 核心逻辑 :通过
DataFetcher(如HttpUrlFetcher)从网络、本地文件或ContentProvider等源头拉取原始图片流。
第8步:写入磁盘缓存(先写原始图,后写转换图)
- 执行位置 :
decodeFromSource()内部 - 核心逻辑 (按写入顺序):
- 获取到原始图片流后,立即写入原始图片到磁盘(Key为简化Key)。
- 对图片进行尺寸转换/压缩处理。
- 处理完成后,写入转换后的图片到磁盘(Key为完整EngineKey)。
- 限制 :具体写入哪一类,取决于你配置的
DiskCacheStrategy。
第9步:写入内存缓存(先写弱引用,后写LruCache)
- 执行位置 :
EngineJob.handleResultOnMainThread()(切回主线程后) - 核心逻辑 (极其重要):
- 图片加载完成后,先存入弱引用缓存(activeResources) ,此时图片正在被
ImageView使用,通过acquired引用计数器标记为"活跃"。 - 当图片不再被任何View使用(
acquired降为0)时,Glide会自动将其从弱引用区移除 ,并存入LruResourceCache(Lru算法区)。
- 图片加载完成后,先存入弱引用缓存(activeResources) ,此时图片正在被
- 设计目的:正在用的图片防回收,不用的图片按最近最少使用原则淘汰。
第10步:回调显示图片
- 执行位置 :
ResourceCallback.onResourceReady() - 核心逻辑 :最终通过
into(imageView)将第9步准备好的EngineResource里的Bitmap/Drawable设置到ImageView上,完成整个闭环。
核心顺序
读取顺序 :内存LruCache → 内存弱引用 → 磁盘转换图 → 磁盘原始图 → 网络
写入顺序:网络获取 → 写磁盘原始图 → 写磁盘转换图 → 写内存弱引用 → (释放后)写内存LruCache
你说得对,面试官追问"为什么这么设计"才是真正考察深度的环节。单纯背流程确实不够,下面我专门从设计动机 和底层权衡的角度,为你补充这一块的核心逻辑:
设计思想
1. 内存缓存:为什么非要拆成"正在使用(Active)"和"缓存池(Lru)"?
- 防止"抖动"与"频繁回收" :如果正在屏幕上显示的图片被放入普通的
LruCache,当内存紧张时,它完全可能被算法回收。但此时用户正盯着这张图,回收后必须立即重新解码(即使从磁盘读,也有IO开销),会导致RecyclerView 滑动卡顿或页面闪烁。 - 利用"引用计数"绑定生命周期 :Glide 通过
acquired变量记录图片被 View 持有的次数。大于 0 表示"活着",放在 弱引用(ActiveResources) 中,既不会被 GC 回收,也不会占用 LruCache 的宝贵名额(给真正不用的图片腾空间)。一旦计数归零(界面销毁或滑出屏幕),马上降级到LruCache。 - 结论:这种设计本质是**"热数据隔离"**,确保最高优先级的渲染资源绝对安全,同时最大化内存利用率。
2. 磁盘缓存:为什么非要区分"原始图(Source)"和"转换图(Result)"?
- 节省 CPU 开销 :图片加载到 View 通常需要**压缩、裁剪、圆角、滤镜(Transform)**等操作,这些是昂贵的 CPU 计算。默认缓存
RESULT(转换后),意味着下次加载同一张图片时,直接跳过解码和变换的步骤,极速显示。 - 适配不同尺寸的 View :如果缓存只存固定宽高的
RESULT,当图片要在大图预览 和列表缩略图 两种场景下显示时,尺寸不匹配会导致模糊或拉伸。此时缓存SOURCE(原始图)就能针对新的宽高重新生成 Result,而无需重新下载网络数据,在"节省流量"和"节省 CPU"之间做了巧妙平衡。 - 结论 :这是一种空间换时间 + 二次加工 的思想,把"下载"和"处理"解耦,让开发者根据 UI 场景(
ALL/SOURCE/RESULT)按需权衡。
3. 缓存 Key:为什么要把"宽高、变换"等十几个参数都塞进去?
- 保证结果的唯一性与正确性 :同样一张网络图片,加载到
100x100的圆角 ImageView 和500x500的普通 ImageView,处理后的Bitmap对象完全不同。如果不区分这些参数,直接返回缓存,会导致图片变形、圆角失效或显示错误。 - 签名(Signature)的防御作用 :支持开发者放入版本号或文件修改时间。当 URL 不变但服务端图片内容更新时,开发者可以通过修改
signature强制刷新缓存,这是单纯依赖 URL 无法做到的。
4. 写入顺序:为什么加载完先写磁盘,再写内存?
- 先写磁盘:写入磁盘是 I/O 操作(较慢),在子线程中完成,不影响主线程;同时保证即使 App 进程被杀,下次启动依然有缓存。
- 后写内存 :内存写入在主线程(或切回主线程后)进行,需要确保
Bitmap已经被完全解码准备妥当。且先写入ActiveResources(使用中),再通过引用计数降到LruCache,完美衔接了"刚加载完正在展示"和"展示完待回收"的生命周期过渡。
5. 深度追问:为什么不直接用系统提供的 Http 缓存或简单的 LruCache?
- 系统的 Http 缓存 :依赖 Response Header(如
Cache-Control),服务端不可控且无法识别 Android 本地对图片的变换处理。 - 简单的 LruCache:只解决内存不够用时的淘汰问题,解决不了"图片正在用却被淘汰"的可见性问题,也缺乏与磁盘缓存的联动逻辑。
- Glide 的巧妙 :通过**"两级内存(Active + Lru)+ 两级磁盘(Source + Result)"**的矩阵组合,本质是将"图片加载生命周期"拆解为 使用态、备用态、原始态和加工态,在内存抖动、滑动帧率、磁盘空间和网络流量之间找到了最佳的工程平衡点。
总结:
"Glide 的缓存设计不是为了简单存数据,而是为了极致地贴合 Android UI 渲染生命周期 。它通过引用计数把'活着'的图片和'待回收'的图片隔离,通过加工前后的分离把'下载成本'和'计算成本'解耦。这一切的核心目标只有一个:在手机内存严苛的环境下,保证用户滑动时绝对不卡顿,同时用最少的流量复现最清晰的画面。"