Glide 加载图片请求 到 完整显示

完整流程梳理

执行时间:

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.RESULTALL 模式下生效。

第6步:读取磁盘缓存(后读原始图片)

  • 执行位置decodeFromCache() -> decodeSourceFromCache()
  • 核心逻辑 :若上一步没找到,则使用简化Key (仅含URL和signature,忽略宽高等参数),查找原始尺寸的图片
  • 适用策略DiskCacheStrategy.SOURCEALL 模式下生效。
  • 结果处理
    • 磁盘命中:解码并返回图片,跳至第9步(写入内存缓存并显示)。
    • 磁盘未命中:进入第7步(网络加载)。

第7步:从网络/源头获取图片资源

  • 执行位置decodeFromSource() -> fetcher.loadData()
  • 核心逻辑 :通过DataFetcher(如HttpUrlFetcher)从网络、本地文件或ContentProvider等源头拉取原始图片流。

第8步:写入磁盘缓存(先写原始图,后写转换图)

  • 执行位置decodeFromSource() 内部
  • 核心逻辑 (按写入顺序):
    1. 获取到原始图片流后,立即写入原始图片到磁盘(Key为简化Key)。
    2. 对图片进行尺寸转换/压缩处理。
    3. 处理完成后,写入转换后的图片到磁盘(Key为完整EngineKey)。
  • 限制 :具体写入哪一类,取决于你配置的DiskCacheStrategy

第9步:写入内存缓存(先写弱引用,后写LruCache)

  • 执行位置EngineJob.handleResultOnMainThread()(切回主线程后)
  • 核心逻辑 (极其重要):
    1. 图片加载完成后,先存入弱引用缓存(activeResources) ,此时图片正在被ImageView使用,通过acquired引用计数器标记为"活跃"。
    2. 当图片不再被任何View使用(acquired降为0)时,Glide会自动将其从弱引用区移除 ,并存入LruResourceCache(Lru算法区)。
  • 设计目的:正在用的图片防回收,不用的图片按最近最少使用原则淘汰。

第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 渲染生命周期 。它通过引用计数把'活着'的图片和'待回收'的图片隔离,通过加工前后的分离把'下载成本'和'计算成本'解耦。这一切的核心目标只有一个:在手机内存严苛的环境下,保证用户滑动时绝对不卡顿,同时用最少的流量复现最清晰的画面。"

相关推荐
Coffeeee2 个月前
如何使用Glide和Coil加载WebP动图
android·kotlin·glide
魏思凡3 个月前
Glide 源码学习系列
源码·glide
帅次4 个月前
链路到端上:HTTPS 之后安全题还在考什么
android·okhttp·glide·zygote·retrofit
studyForMokey4 个月前
【Android面试】Glide专题
android·面试·glide
花卷HJ5 个月前
[特殊字符] Glide 图片加载优化:自定义队列管理器(支持并发控制 + 防抖 + 滑动优化)
glide
Greenland_125 个月前
Android Java使用Glide无法生成GlideApp
android·java·glide
pvIaUtLZ6 个月前
每个支路的三相阻抗矩阵
glide
CjQYqIyjLwCW7 个月前
Halcon联合C#开发最新版实用框架 实际项目应用验证过的版本,源码,修改了大量Bug以适合...
glide
a3158238068 个月前
Android 大图显示策略优化显示(二)
android·java·开发语言·javascript·kotlin·glide·图片加载