Glide 图片加载原理:三级缓存、生命周期绑定与内存优化
图片加载是 Android 开发中最常见的需求之一,但直接用 BitmapFactory 很快会遇到内存溢出、重复加载、生命周期管理等问题。Glide 作为 Google 推荐的图片加载库,通过三级缓存、生命周期绑定和内存优化策略,优雅地解决了这些痛点。
本文深入 Glide 的核心机制,从源码层面拆解它如何管理图片的加载、缓存与资源回收。
为什么不直接用 BitmapFactory?
直接加载图片面临的典型问题:
kotlin
// ❌ 常见错误写法
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 的加载流程可以简化为:
scss
Glide.with(context)
.load(url)
.into(imageView)
背后经历了 生命周期感知 → 缓存检查 → 解码 → 转换 → 显示 的完整链路。
1. 生命周期绑定
Glide 通过隐藏的 Fragment 监听生命周期:
java
// 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 回收:
java
// 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)策略管理内存:
java
// 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;
}
内存限制计算:
java
// MemorySizeCalculator.java
int targetMemoryCacheSize = (int) (maxSize * memoryCacheScreens);
// 默认保留 2 个屏幕的图片
磁盘缓存(DiskLruCache)
网络图片的原始数据和处理后的结果都会缓存到磁盘:
kotlin
// 配置磁盘缓存策略
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL) // 原图 + 结果
.into(imageView)
DiskCacheStrategy 选项:
NONE:不缓存RESOURCE:只缓存转换后的图片DATA:只缓存原始图片ALL:缓存原始图片和转换后的结果(默认)AUTOMATIC:智能选择策略
磁盘缓存使用 DiskLruCache 实现,通过 SHA-256 的 URL 生成缓存 Key:
java
// DataCacheKey.java
@Override
public String toString() {
return "DataCacheKey{"
+ "sourceKey=" + sourceKey
+ ", signature=" + signature
+ '}';
}
内存优化核心策略
1. 图片降采样(Downsampling)
加载大图时自动计算采样率:
java
// 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 内存块:
java
// 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 字节/像素):
kotlin
// 手动指定格式
Glide.with(this)
.load(url)
.format(DecodeFormat.PREFER_RGB_565)
.into(imageView)
权衡:
RGB_565:不支持透明度,但内存占用减半ARGB_8888:支持透明度,适合 PNG
加载流程源码解析
完整的加载链路:
scss
RequestBuilder.into()
↓
SingleRequest.begin()
↓
Engine.load()
↓
[检查活动资源] → [检查内存缓存] → [检查磁盘缓存] → [网络请求]
↓
DecodeJob.run() (在后台线程)
↓
Downsampler.decode()
↓
Transformation.transform() (如 CenterCrop)
↓
ResourceTranscoder.transcode()
↓
Target.onResourceReady() (回到主线程)
核心类职责
| 类 | 职责 |
|---|---|
RequestManager |
管理请求生命周期 |
Engine |
缓存查询与任务调度 |
DecodeJob |
在后台线程解码图片 |
Downsampler |
降采样逻辑 |
BitmapPool |
Bitmap 复用池 |
Target |
将资源设置到 View |
常见使用场景与优化
1. 圆形裁剪
kotlin
Glide.with(this)
.load(url)
.circleCrop()
.into(imageView)
内部实现:
java
// CircleCrop.java
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);
}
2. 占位图与错误处理
kotlin
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading) // 加载中
.error(R.drawable.error) // 加载失败
.into(imageView)
3. 缩略图策略
kotlin
Glide.with(this)
.load(url)
.thumbnail(0.1f) // 先加载 10% 尺寸的缩略图
.into(imageView)
效果 :
用户先看到模糊的预览图,提升体验。
4. 预加载
kotlin
Glide.with(this)
.load(url)
.preload(500, 500)
场景 :
在列表滑动前,提前加载即将显示的图片。
与 Picasso、Coil 的对比
| 特性 | Glide | Picasso | Coil |
|---|---|---|---|
| 支持 GIF | ✅ | ❌ | ✅ |
| Bitmap 复用 | ✅ | ❌ | ✅ |
| 生命周期绑定 | 自动 | 手动 | 自动 |
| Kotlin 协程 | 通过扩展 | ❌ | 原生支持 |
| 磁盘缓存 | DiskLruCache | OkHttp 缓存 | OkHttp 缓存 |
结论:
- Glide:功能最全,适合复杂场景
- Coil:Kotlin 优先,协程友好,体积小
- Picasso:简单易用,但功能相对受限
最佳实践建议
- 统一配置全局 Glide
kotlin
@GlideModule
class MyAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setMemoryCache(LruResourceCache(20 * 1024 * 1024))
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 100 * 1024 * 1024))
}
}
- 避免过度转换
kotlin
// ❌ 每次都转换
Glide.with(this).load(url).circleCrop().into(imageView)
// ✅ 服务端返回已裁剪好的图片
Glide.with(this).load(croppedUrl).into(imageView)
- 列表优化
kotlin
// 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 通过以下机制解决图片加载的核心问题:
- 三级缓存:活动资源 → 内存 LRU → 磁盘,减少重复加载
- 生命周期绑定:自动取消请求,避免内存泄漏
- 内存优化:降采样、Bitmap 复用、格式选择
- 灵活配置:支持占位图、转换、缩略图、预加载
理解这些原理,不仅能用好 Glide,也能在遇到自定义需求时,知道从哪个环节入手优化。
延伸思考 :
如果项目需要加载超大图(如地图瓦片),Glide 的缓存策略可能不适用,需要考虑 SubsamplingScaleImageView 等方案。图片加载的优化,永远是在功能、性能和体验之间找平衡。