Glide 原理分析

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 的原理就基本掌握了。

相关推荐
清泓y2 小时前
UE 物理系统知识分享
面试·ue5·游戏程序
KaifuZeng3 小时前
电源面试问题汇总一
单片机·嵌入式硬件·面试·电路
HeiSenBerg3 小时前
ARouter 原理深度剖析:从 APT 到 ASM,彻底搞懂路由框架
面试
程序员清风3 小时前
AI不是万能的,大家要专注实践!
java·后端·面试
清泓y3 小时前
UE移动开发技术面试题
android·面试·ue5·ue4·游戏程序
刘沅4 小时前
Redis的哨兵机制
后端·面试
凉凉的知识库4 小时前
什么是 HTTP Keep-Alive?一文讲清连接复用机制
网络协议·http·面试
清泓y5 小时前
UE基础知识与引擎架构面试题
面试·架构·ue5·ue4·游戏程序
触底反弹5 小时前
💡 React 父子组件通信:一个进度条教会我的 5 件事
前端·react.js·面试