[Android 从零到一] Glide 图片加载原理:三级缓存、生命周期绑定与内存优化

13 阅读6分钟

Glide 图片加载原理:三级缓存、生命周期绑定与内存优化

图片加载是 Android 开发中最常见的需求之一,但直接用 BitmapFactory 很快会遇到内存溢出、重复加载、生命周期管理等问题。Glide 作为 Google 推荐的图片加载库,通过三级缓存、生命周期绑定和内存优化策略,优雅地解决了这些痛点。

本文深入 Glide 的核心机制,从源码层面拆解它如何管理图片的加载、缓存与资源回收。


为什么不直接用 BitmapFactory?

直接加载图片面临的典型问题:

// ❌ 常见错误写法
val bitmap = BitmapFactory.decodeFile("/sdcard/large_image.jpg")
imageView.setImageBitmap(bitmap)

问题 1:内存溢出
一张 4000×3000 的图片,ARGB_8888 格式需要 4000 * 3000 * 4 = 48MB 内存,很容易触发 OOM。

问题 2:主线程阻塞
网络图片加载在主线程会导致 ANR。

问题 3:生命周期管理
Activity 已销毁时,回调设置 Bitmap 会导致内存泄漏或崩溃。

问题 4:重复加载
同一个 URL 在列表中反复加载,浪费流量和内存。

Glide 针对性地解决了这些问题。


Glide 的核心架构

Glide 的加载流程可以简化为:

Glide.with(context)
  .load(url)
  .into(imageView)

背后经历了 生命周期感知 → 缓存检查 → 解码 → 转换 → 显示 的完整链路。

1. 生命周期绑定

Glide 通过隐藏的 Fragment 监听生命周期:

// RequestManagerFragment.java (Glide 内部)
public class RequestManagerFragment extends Fragment {
  private final ActivityFragmentLifecycle lifecycle;

  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
  }
}

关键设计:

  • 调用 Glide.with(activity) 时,Glide 会向当前 Activity 添加一个无 UI 的 Fragment
  • 当 Activity 进入 onStop 时,Glide 暂停所有请求
  • 当 Activity 被销毁时,自动取消请求并清理资源

这样开发者无需手动管理请求的生命周期,避免了内存泄漏。


三级缓存策略

Glide 的缓存分为三层:活动资源缓存(Active Resources) → 内存缓存(Memory Cache) → 磁盘缓存(Disk Cache)。

活动资源缓存(强引用)

正在使用的图片通过强引用保存在 Map<Key, WeakReference<Resource>> 中,避免被 GC 回收:

// ActiveResources.java
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

synchronized void activate(Key key, EngineResource<?> resource) {
  ResourceWeakReference toPut = new ResourceWeakReference(key, resource, getReferenceQueue());
  activeEngineResources.put(key, toPut);
}

特点:

  • 使用中的图片不会被复用或清理
  • 引用计数归零后移入内存缓存

内存缓存(LruCache)

使用 LRU(Least Recently Used)策略管理内存:

// LruResourceCache.java
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);

public synchronized Y put(T key, Y item) {
  final int itemSize = getSize(item);
  if (itemSize >= maxSize) {
    return null; // 单个资源过大,不缓存
  }
  
  currentSize += itemSize;
  Entry<Y> old = cache.put(key, new Entry<>(item, itemSize));
  
  if (old != null) {
    currentSize -= old.size;
  }
  
  evict(); // 超过限制时清理旧资源
  return old != null ? old.value : null;
}

内存限制计算:

// MemorySizeCalculator.java
int targetMemoryCacheSize = (int) (maxSize * memoryCacheScreens);
// 默认保留 2 个屏幕的图片

磁盘缓存(DiskLruCache)

网络图片的原始数据和处理后的结果都会缓存到磁盘:

// 配置磁盘缓存策略
Glide.with(this)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL) // 原图 + 结果
  .into(imageView)

DiskCacheStrategy 选项:

  • NONE:不缓存
  • RESOURCE:只缓存转换后的图片
  • DATA:只缓存原始图片
  • ALL:缓存原始图片和转换后的结果(默认)
  • AUTOMATIC:智能选择策略

磁盘缓存使用 DiskLruCache 实现,通过 SHA-256 的 URL 生成缓存 Key:

// DataCacheKey.java
@Override
public String toString() {
  return "DataCacheKey{"
      + "sourceKey=" + sourceKey
      + ", signature=" + signature
      + '}';
}

内存优化核心策略

1. 图片降采样(Downsampling)

加载大图时自动计算采样率:

// Downsampler.java
int inSampleSize = calculateScaling(...);

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 只读尺寸,不加载像素
BitmapFactory.decodeStream(inputStream, null, options);

options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);

示例:

  • 原图 4000×3000,目标 View 400×300
  • 采样率 inSampleSize = 8,实际加载 500×375 的图片
  • 内存占用从 48MB 降至 0.75MB

2. Bitmap 复用(BitmapPool)

Glide 维护一个 BitmapPool,复用已释放的 Bitmap 内存块:

// LruBitmapPool.java
public synchronized Bitmap getDirty(int width, int height, Bitmap.Config config) {
  Bitmap result = strategy.get(width, height, config);
  if (result != null) {
    currentSize -= strategy.getSize(result);
  }
  return result;
}

效果:
避免频繁的 Bitmap 创建和 GC,减少内存抖动。

3. 图片格式选择

Glide 默认使用 RGB_565(2 字节/像素)替代 ARGB_8888(4 字节/像素):

// 手动指定格式
Glide.with(this)
  .load(url)
  .format(DecodeFormat.PREFER_RGB_565)
  .into(imageView)

权衡:

  • RGB_565:不支持透明度,但内存占用减半
  • ARGB_8888:支持透明度,适合 PNG

加载流程源码解析

完整的加载链路:

RequestBuilder.into()
  ↓
SingleRequest.begin()
  ↓
Engine.load()
  ↓
[检查活动资源] → [检查内存缓存] → [检查磁盘缓存] → [网络请求]
  ↓
DecodeJob.run() (在后台线程)
  ↓
Downsampler.decode()
  ↓
Transformation.transform() (如 CenterCrop)
  ↓
ResourceTranscoder.transcode()
  ↓
Target.onResourceReady() (回到主线程)

核心类职责

类职责
RequestManager管理请求生命周期
Engine缓存查询与任务调度
DecodeJob在后台线程解码图片
Downsampler降采样逻辑
BitmapPoolBitmap 复用池
Target将资源设置到 View

常见使用场景与优化

1. 圆形裁剪

Glide.with(this)
  .load(url)
  .circleCrop()
  .into(imageView)

内部实现:

// CircleCrop.java
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
  return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);
}

2. 占位图与错误处理

Glide.with(this)
  .load(url)
  .placeholder(R.drawable.loading) // 加载中
  .error(R.drawable.error) // 加载失败
  .into(imageView)

3. 缩略图策略

Glide.with(this)
  .load(url)
  .thumbnail(0.1f) // 先加载 10% 尺寸的缩略图
  .into(imageView)

效果:
用户先看到模糊的预览图,提升体验。

4. 预加载

Glide.with(this)
  .load(url)
  .preload(500, 500)

场景:
在列表滑动前,提前加载即将显示的图片。


与 Picasso、Coil 的对比

特性GlidePicassoCoil
支持 GIF✅❌✅
Bitmap 复用✅❌✅
生命周期绑定自动手动自动
Kotlin 协程通过扩展❌原生支持
磁盘缓存DiskLruCacheOkHttp 缓存OkHttp 缓存

结论:

  • Glide:功能最全,适合复杂场景
  • Coil:Kotlin 优先,协程友好,体积小
  • Picasso:简单易用,但功能相对受限

最佳实践建议

  1. 统一配置全局 Glide
@GlideModule
class MyAppGlideModule : AppGlideModule() {
  override fun applyOptions(context: Context, builder: GlideBuilder) {
    builder.setMemoryCache(LruResourceCache(20 * 1024 * 1024))
    builder.setDiskCache(InternalCacheDiskCacheFactory(context, 100 * 1024 * 1024))
  }
}
  1. 避免过度转换
// ❌ 每次都转换
Glide.with(this).load(url).circleCrop().into(imageView)

// ✅ 服务端返回已裁剪好的图片
Glide.with(this).load(croppedUrl).into(imageView)
  1. 列表优化
// RecyclerView 滑动监听
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
  override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
      Glide.with(this@MainActivity).resumeRequests()
    } else {
      Glide.with(this@MainActivity).pauseRequests()
    }
  }
})

总结

Glide 通过以下机制解决图片加载的核心问题:

  1. 三级缓存:活动资源 → 内存 LRU → 磁盘,减少重复加载
  2. 生命周期绑定:自动取消请求,避免内存泄漏
  3. 内存优化:降采样、Bitmap 复用、格式选择
  4. 灵活配置:支持占位图、转换、缩略图、预加载

理解这些原理,不仅能用好 Glide,也能在遇到自定义需求时,知道从哪个环节入手优化。


延伸思考:
如果项目需要加载超大图(如地图瓦片),Glide 的缓存策略可能不适用,需要考虑 SubsamplingScaleImageView 等方案。图片加载的优化,永远是在功能、性能和体验之间找平衡。