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

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:简单易用,但功能相对受限

最佳实践建议

  1. 统一配置全局 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))
  }
}
  1. 避免过度转换
kotlin 复制代码
// ❌ 每次都转换
Glide.with(this).load(url).circleCrop().into(imageView)

// ✅ 服务端返回已裁剪好的图片
Glide.with(this).load(croppedUrl).into(imageView)
  1. 列表优化
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 通过以下机制解决图片加载的核心问题:

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

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


延伸思考 :

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

相关推荐
法的空间1 小时前
AI 编程时代,为什么 Dart 分析会拖慢大型项目的开发
android·flutter·ios
鱼儿也有烦恼1 小时前
客户端应用架构模式
android·架构
独隅2 小时前
Claude 高效应用实战指南
android·java·开发语言·claude·claude code
硅基手札2 小时前
Android 系统全景专栏 00 · 总目录
android·操作系统·嵌入式开发
YB13752 小时前
Jetpack Compose 动画Crossfade
android
2401_833269303 小时前
Android歌词功能实现
android
恋猫de小郭3 小时前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
YB13753 小时前
Jetpack Compose 动画AnimatedContent
android
美狐美颜SDK开放平台14 小时前
准备开发直播APP?视频美颜SDK接入前的十项检查清单
android·ios·音视频·直播美颜sdk·视频美颜sdk