图片加载是 Android 开发中最常见的需求之一,但直接用 BitmapFactory 很快会遇到内存溢出、重复加载、生命周期管理等问题。Glide 作为 Google 推荐的图片加载库,通过三级缓存、生命周期绑定和内存优化策略,优雅地解决了这些痛点。
本文深入 Glide 的核心机制,从源码层面拆解它如何管理图片的加载、缓存与资源回收。
直接加载图片面临的典型问题:
// ❌ 常见错误写法
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.with(context)
.load(url)
.into(imageView)背后经历了 生命周期感知 → 缓存检查 → 解码 → 转换 → 显示 的完整链路。
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);
}特点: - 使用中的图片不会被复用或清理 - 引用计数归零后移入内存缓存
使用 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 个屏幕的图片网络图片的原始数据和处理后的结果都会缓存到磁盘:
// 配置磁盘缓存策略
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
+ '}';
}加载大图时自动计算采样率:
// 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
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,减少内存抖动。
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 | 降采样逻辑 |
BitmapPool | Bitmap 复用池 |
Target | 将资源设置到 View |
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);
}Glide.with(this)
.load(url)
.placeholder(R.drawable.loading) // 加载中
.error(R.drawable.error) // 加载失败
.into(imageView)Glide.with(this)
.load(url)
.thumbnail(0.1f) // 先加载 10% 尺寸的缩略图
.into(imageView)效果: 用户先看到模糊的预览图,提升体验。
Glide.with(this)
.load(url)
.preload(500, 500)场景: 在列表滑动前,提前加载即将显示的图片。
特性 | Glide | Picasso | Coil |
|---|---|---|---|
支持 GIF | ✅ | ❌ | ✅ |
Bitmap 复用 | ✅ | ❌ | ✅ |
生命周期绑定 | 自动 | 手动 | 自动 |
Kotlin 协程 | 通过扩展 | ❌ | 原生支持 |
磁盘缓存 | DiskLruCache | OkHttp 缓存 | OkHttp 缓存 |
结论: - Glide:功能最全,适合复杂场景 - Coil:Kotlin 优先,协程友好,体积小 - Picasso:简单易用,但功能相对受限
@GlideModule
class MyAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setMemoryCache(LruResourceCache(20 * 1024 * 1024))
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 100 * 1024 * 1024))
}
}// ❌ 每次都转换
Glide.with(this).load(url).circleCrop().into(imageView)
// ✅ 服务端返回已裁剪好的图片
Glide.with(this).load(croppedUrl).into(imageView)// 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 通过以下机制解决图片加载的核心问题:
理解这些原理,不仅能用好 Glide,也能在遇到自定义需求时,知道从哪个环节入手优化。
延伸思考:
如果项目需要加载超大图(如地图瓦片),Glide 的缓存策略可能不适用,需要考虑 SubsamplingScaleImageView 等方案。图片加载的优化,永远是在功能、性能和体验之间找平衡。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。