Glide 原理分析
Glide 是 Google 官方推荐的 Android 图片加载框架,核心机制可以概括为:生命周期绑定 + 三级缓存 + BitmapPool 复用。本文从源码角度,把 Glide 四大核心模块逐一拆解。
一、with() ------ 生命周期绑定
java
Glide.with(activity).load(url).into(imageView);
Glide.with() 返回的是一个 RequestManager 对象,这个对象里管理着三个关键回调:
scss
onStart() → 恢复图片加载
onStop() → 暂停图片加载
onDestroy() → 取消所有请求,销毁资源
它的核心使命就是绑定宿主的生命周期。
实现原理
根据 with() 传入的上下文对象(Activity / Fragment / View / Context),Glide 最终会获取到所在的 Activity。然后创建一个空白的 Fragment(RequestManagerFragment) ,通过 FragmentManager 挂载到这个 Activity 上。
java
Fragment fragment = new RequestManagerFragment();
activity.getSupportFragmentManager()
.beginTransaction()
.add(fragment, TAG)
.commitAllowingStateLoss();
这个空白 Fragment 本身不占任何 UI,但它能收到完整的生命周期回调:
scss
Activity.onStart() → Fragment.onStart() → RequestManager.onStart()
Activity.onStop() → Fragment.onStop() → RequestManager.onStop()
Activity.onDestroy() → Fragment.onDestroy() → RequestManager.onDestroy()
这里的设计是观察者模式------Fragment 作为被观察者,RequestManager 作为观察者,生命周期变化时通过回调链层层通知。
特殊情形:ApplicationContext
当传入的是 ApplicationContext 时,Glide 不会创建空白 Fragment,因为 Application 没有可绑定的生命周期概念。这种情况下图片加载的存活周期跟随进程,直到被主动取消。
二、load() ------ 构建请求
load() 返回一个 RequestBuilder,为后续的图片加载做好准备。支持多种资源类型:
java
.load(url) // 网络图片
.load(file) // 本地文件
.load(resourceId) // 资源文件
.load(uri) // Uri
.load(byte[]) // 字节数组
RequestBuilder 内部记录了本次请求的所有信息:
- 资源来源(url / file / resourceId ...)
- 可选配置项(placeholder、error、transform、diskCacheStrategy 等)
三、into(ImageView) ------ 核心加载流程
into() 是 Glide 最核心的部分,整个加载链条的枢纽。
执行链路
scss
into(ImageView)
→ 为 ImageView 创建 Target
→ 构建 SingleRequest
→ 交给 Engine 发起加载
→ Engine 根据 url、尺寸、变换、签名等生成 EngineKey
→ Engine.load() → loadFromMemory()
→ ① 查 ActiveResources
→ ② 查 LruResourceCache(命中则从 LruCache 移除,移入 ActiveResources)
→ 都没有 → 启动 EngineJob 异步加载
→ 下载/磁盘读取 → 解码 → EngineJob 完成
→ onEngineJobComplete() → ActiveResources.activate()
→ 回调 Target 展示
EngineKey 的作用
EngineKey 唯一标识一次图片请求,由以下因素组合生成:
ini
EngineKey = url + targetWidth + targetHeight + signature + transformKey + ...
相同 Key 的请求可以被复用,避免同一张图重复加载。
四、三级缓存机制
这是 Glide 性能的核心所在。Glide 把缓存分为三个层级,查找时按顺序访问。
4.1 缓存结构
php
┌─────────────────────────────────────────────┐
│ Glide 三级缓存架构 │
├─────────────┬───────────────┬────────────────┤
│ 活动缓存 │ 内存缓存 │ 磁盘缓存 │
│ ActiveResources│ LruResourceCache│ DiskLruCache │
├─────────────┼───────────────┼────────────────┤
│ WeakReference│ StrongReference│ 文件持久化 │
│ 正在展示的图片 │ 近期展示过的 │ 原始图 / 转换图 │
│ (互斥转移) │ (互斥转移) │ │
└─────────────┴───────────────┴────────────────┘
关键设计:ActiveResources 和 LruResourceCache 是互斥的。 一张图片在任意时刻只存在于其中一个,不会同时存在于两个缓存中。资源在这两者之间"转移"。
4.2 查找顺序
scss
Engine.load() 被调用
│
├─① loadFromActiveResources(key)
│ └─ ActiveResources 中命中 → acquire() + 直接返回
│
├─② loadFromCache(key)
│ ├─ cache.remove(key) ← 从 LruCache 移除
│ ├─ cached.acquire() ← 引用计数 +1
│ ├─ activeResources.activate() ← 移入 ActiveResources
│ └─ 返回
│
└─③ 都没有 → 启动 EngineJob 异步加载
对应的源码:
java
// Engine.java - loadFromMemory()
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
// ① 先查 ActiveResources
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) return active;
// ② 再查 LruCache
EngineResource<?> cached = loadFromCache(key);
if (cached != null) return cached;
return null;
}
// loadFromCache() 内部:从 LruCache 移除并移入 ActiveResources
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
// getEngineResourceFromCache → cache.remove(key) ← 从 LruCache 删掉!
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached); // 移入 ActiveResources
}
return cached;
}
4.3 网络下载后的写入路径
这是跟之前最大的不同------新加载的图片不会经过 LruCache,而是直接进入 ActiveResources。
java
// Engine.java - onEngineJobComplete()
@Override
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
if (resource != null && resource.isMemoryCacheable()) {
// ★ 直接激活到 ActiveResources,不经过 LruCache
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
scss
网络下载 → 磁盘缓存(写入)
→ 解码为 Bitmap
→ EngineJob 完成
→ onEngineJobComplete()
→ activeResources.activate(key, resource) ← 直接进 ActiveResources!
→ 回调 Target → ImageView 展示
4.4 不再展示时:从 ActiveResources 退回 LruCache
java
// Engine.java - onResourceReleased()
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey); // 从 ActiveResources 移除
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource); // ★ 放入 LruCache
} else {
resourceRecycler.recycle(resource, false);
}
}
scss
图片不再展示(页面关闭 / View 释放)
→ Engine.release(resource)
→ EngineResource.release() → 引用计数减到 0
→ onResourceReleased()
→ activeResources.deactivate(key) ← 从 ActiveResources 删除
→ cache.put(key, resource) ← 放入 LruCache,等待下次命中
4.5 完整生命周期
scss
新加载一张图
┌──────────────────────────────────────────┐
│ 网络 → 解码 → ActiveResources(先激活) │ ← 激活
│ → ImageView 展示 │
│ → ENCODE → 磁盘(后写入) │ ← 写入磁盘
│ → 页面关闭/View 释放 │
│ → deactivate + cache.put → LruCache │ ← 退回
└──────────────────────────────────────────┘
再次打开页面,同一张图
┌──────────────────────────────────────────┐
│ loadFromCache() │
│ → cache.remove(key) ← 从 LruCache 移除 │
│ → activate → ActiveResources │ ← 再次激活
│ → ImageView 展示 │
│ → 页面关闭 │
│ → deactivate + cache.put → LruCache │ ← 退回
└──────────────────────────────────────────┘
LruCache 空间满了
→ LRU 淘汰最早未使用的
→ onResourceRemoved() → recycle() → Bitmap 回收
4.6 设计要点
1. ActiveResources 和 LruCache 互斥转移
一张图片在任意时刻只存在于其中一个缓存中,不会同时出现。这是为了精确控制内存预算------如果同一份资源在两个缓存中各存一份,LruCache 的容量上限就形同虚设。
2. 新加载的图片为什么不先放 LruCache?
因为马上要展示,而展示期间由 ActiveResources 管理。先放 LruCache 再转移到 ActiveResources 是多此一举。直接进 ActiveResources,不再展示时再退到 LruCache,路径最短。
3. 为什么先激活后写磁盘?
DecodeJob.notifyEncodeAndRelease() 中,notifyComplete()(触发 ActiveResources 激活)在 Stage.ENCODE(写磁盘)之前执行。因为图片已解码为 Bitmap,应优先让用户看到,磁盘写入可以慢一步完成。
java
// DecodeJob.java 真实顺序
notifyComplete(result, ...); // ① 先激活 → 展示
deferredEncodeManager.encode(diskCacheProvider, options); // ② 后写磁盘
4.7 LruCache 原理
LruCache 全称 Least Recently Used Cache(最近最少使用缓存),是 Glide 内存缓存的核心实现。
数据结构
java
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> map; // 核心容器
private int maxSize; // 最大容量
private int size; // 当前大小
}
LruCache 内部封装了一个 LinkedHashMap ,并利用它的 access ordering 模式实现 LRU 淘汰:
java
// LinkedHashMap 构造函数的第三个参数 accessOrder
// true = 按访问顺序排序,false = 按插入顺序排序
LinkedHashMap<K, V> map = new LinkedHashMap<>(0, 0.75f, true);
当 accessOrder = true 时,每次 get() 或 put() 操作一个元素,该元素会被移动到链表尾部 。因此链表头部始终是最近最少使用的元素,放入一个元素发现超出容量时,直接移除链表头部的元素即可。
java
// LruCache 核心 put 逻辑(简化)
public final V put(K key, V value) {
V previous;
synchronized (this) {
previous = map.put(key, value);
size += safeSizeOf(key, value); // 更新当前大小
}
// 如果超出最大容量,移除最少使用的
if (size > maxSize) {
trimToSize(maxSize);
}
return previous;
}
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(...);
}
if (size <= maxSize || map.isEmpty()) {
break;
}
// 获取链表头部(最少使用的)并移除
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
}
}
}
Glide 中的定制 :Glide 的 LruResourceCache 继承自 Android 原生的 LruCache,设置了最大容量为当前 App 可用堆内存的 1/8:
java
// Glide 默认配置
int memoryCacheSize = Runtime.getRuntime().maxMemory() / 8;
LruResourceCache cache = new LruResourceCache(memoryCacheSize);
工作示例
假设 LruCache 最大容量为 4:
css
初始: []
put A → [A]
put B → [A, B]
put C → [A, B, C]
put D → [A, B, C, D] ← 满
get A → [B, C, D, A] ← A 被访问,移到尾部
put E → [C, D, A, E] ← 超容量,移除头部 B
关键结论 :LruCache 的淘汰完全由 LinkedHashMap 的 access ordering 机制自动完成,LruCache 本身只需要在 trimToSize() 中移除当前链表头部即可。
4.8 磁盘缓存策略
Glide 提供了四种磁盘缓存策略:
java
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
| 策略 | 缓存内容 | 适用场景 |
|---|---|---|
AUTOMATIC |
默认。自动选择缓存原始图或转换图 | 大多数场景 |
ALL |
同时缓存原始图和转换后的图 | 同一张图需要多种尺寸/变换 |
DATA |
只缓存原始图 | 只需要原始数据 |
RESOURCE |
只缓存转换后的图 | 始终只使用一种尺寸 |
NONE |
不缓存 | 临时图片、验证码等 |
五、BitmapPool ------ 内存复用
Glide 的一大内存优化是 BitmapPool。它不会在每次解码时都向系统申请新的内存,而是从池子里拿一个大小合适、已废弃的 Bitmap 对象来复用。
java
Bitmap reused = bitmapPool.get(width, height, config);
if (reused != null) {
Bitmap bitmap = decodeStream(stream, options.setBitmap(reused));
}
这样做的好处:
- 减少 GC 触发频率------不再需要每次都 new Bitmap
- 滑动列表更流畅------复用避免了频繁的分配和回收带来的卡顿
- 降低 OOM 风险------池子大小可控,不会无限制分配
六、其他重要特性
6.1 Transform(图片变换)
java
Glide.with(this)
.load(url)
.circleCrop() // 圆形裁剪
.centerCrop() // 居中裁剪
.fitCenter() // 适应居中
.transform(...) // 自定义变换
.into(imageView);
多个 Transform 可以叠加使用。当 diskCacheStrategy 设为 RESOURCE 时,缓存的是变换后的结果,避免每次加载都重新变换。
6.2 图片格式自动识别
Glide 会自动根据 URL 后缀或响应头中的 Content-Type 判断图片格式(静态图、GIF、WebP),并选择合适的解码器。如果需要强制指定:
java
.asBitmap() // 强制作为静态图
.asGif() // 强制作为 Gif
.asFile() // 直接下载为文件
6.3 缩略图支持
java
Glide.with(this)
.load(url)
.thumbnail(0.25f) // 先加载 25% 缩略图,再加载原图
.into(imageView);
原理是同时发起两个请求:一个低分辨率图立即展示(占位),原图加载完成后替换。用户体验远好于白屏等待。
总结
Glide 的核心流程可以概括为:
通过 Fragment 绑定生命周期自动管理请求,用 ActiveResources + LruCache + DiskCache 三级缓存(互斥转移)减少重复加载,用 BitmapPool 复用内存避免频繁 GC,通过线程池异步下载和解码,最终在主线程把 Bitmap 交给 ImageView 展示。
scss
Glide.with(Activity)
.load(url)
.into(imageView)
with()
→ 创建空白 Fragment 监听生命周期
→ 返回 RequestManager
load()
→ 返回 RequestBuilder
into()
→ 构建 Target + SingleRequest
→ Engine.load()
→ loadFromActiveResources(key) ① 活动缓存
→ loadFromCache(key) ② LruCache(命中则移除+激活到活动缓存)
→ 都没有 → 启动 EngineJob
→ 磁盘/网络 → 解码
→ onEngineJobComplete → activate → ActiveResources ③
→ ImageView 展示
不再展示时:
→ Engine.release()
→ onResourceReleased()
→ deactivate + cache.put → LruCache
生命周期变化:
→ Fragment 回调 → RequestManager
→ onStart() 恢复 / onStop() 暂停 / onDestroy() 取消
理解了这个链条,Glide 的原理就基本掌握了。