目录
  1. 1. 一、图片加载框架的核心问题
  2. 2. 二、Glide 的请求处理流水线
    1. 2.1. 2.1 Engine:缓存策略的核心
    2. 2.2. 2.2 三层缓存架构
  3. 3. 三、Bitmap 复用池:LruBitmapPool
  4. 4. 四、图片采样的 Downsampler
  5. 5. 五、生命周期绑定:RequestManagerFragment
  6. 6. 六、Coil:Kotlin 协程的轻量级方案
  7. 7. 七、面试常问题目
解读开源框架系列-图片加载框架设计

一、图片加载框架的核心问题

在 Android 上加载和显示图片,看似简单实则包含了一系列棘手的问题:

  1. 内存管理:Bitmap 是 Android 中最占内存的对象。一张 1024x1024 的 ARGB_8888 图片占用 4MB,而屏幕通常只有 1080px 宽。如果不缩放,列表滑动时会迅速耗尽内存。
  2. 图片复用:创建和回收 Bitmap 的代价很高,需要池化复用。
  3. 生命周期绑定:Activity 销毁后,未完成的图片请求应被取消,否则会导致内存泄漏和崩溃。
  4. 缓存策略:既要内存缓存(快速访问),又要磁盘缓存(持久化),还要在两者之间找到平衡。
  5. 格式适配:不同的图片格式(PNG、JPEG、WebP、GIF、SVG、HEIF)需要不同的解码器。
  6. 变换处理:图片加载后可能需要进行裁剪、圆角、模糊等变换。

Glide(https://github.com/bumptech/glide)是 Google 推荐的图片加载框架,被广泛部署在 Android 应用中。Coil(https://github.com/coil-kt/coil)是 Kotlin 生态的后起之秀。

二、Glide 的请求处理流水线

Glide 的图片加载流水线是一套分层解耦的流水线,每个节点有明确的职责:

RequestManager (生命周期管理)

RequestBuilder (配置请求参数:URL、大小、变换、占位图等)

Request (代表一次加载任务:SingleRequest / ThumbnailRequestCoordination)

Engine (加载引擎,负责缓存策略,协调内存缓存/磁盘缓存/网络请求)

DecodeJob (解码任务,实现 Runnable,提交到线程池执行)

┌────────────────────────────────────────────────────┐
│ DecodeJob.run() → runGenerators() │
│ 1. ResourceCacheGenerator (资源缓存) │
│ 2. DataCacheGenerator (数据缓存) │
│ 3. SourceGenerator (从源加载:网络/文件) │
└────────────────────────────────────────────────────┘

ModelLoader (数据加载:HttpUrlFetcher, FileFetcher 等)

DecodePath (解码:将 InputStream 解码为 Bitmap/Drawable)

Transformation (变换:CenterCrop, CircleCrop, RoundedCorners 等)

Target → ImageView (最终显示)

2.1 Engine:缓存策略的核心

// com.bumptech.glide.load.engine.Engine(简化)
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {

private final MemoryCache cache; // LruResourceCache(内存缓存)
private final Map<Key, WeakReference<EngineResource<?>>> activeResources; // 活跃资源
private final EngineJobFactory engineJobFactory;
private final DecodeJobFactory decodeJobFactory;

public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width, int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb, Executor callbackExecutor) {

EngineKey key = keyFactory.buildKey(model, signature, width, height,
transformations, resourceClass, transcodeClass, options);

// 1. 从活跃资源中查找(弱引用 Map,持有正在使用的资源)
EngineResource<?> memoryResource = loadFromActiveResources(key, isMemoryCacheable);
if (memoryResource != null) {
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}

// 2. 从内存缓存中查找(LRU 缓存)
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}

// 3. 内存缓存未命中 → 启动 DecodeJob 从磁盘或网络加载
EngineJob<R> engineJob = engineJobFactory.build(
key, isMemoryCacheable, useUnlimitedSourceExecutorPool,
useAnimationPool, onlyRetrieveFromCache);

DecodeJob<R> decodeJob = decodeJobFactory.build(
glideContext, model, key, signature, width, height,
resourceClass, transcodeClass, priority, diskCacheStrategy,
transformations, isTransformationRequired, isScaleOnlyOrNoTransform,
options, engineJob);

jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);

return new LoadStatus(cb, engineJob);
}
}

2.2 三层缓存架构

Glide 的内存缓存分为两层:

第一层:活跃资源(Active Resources)——Map<Key, WeakReference<EngineResource<?>>>

这是当前正在被显示的图片。EngineResource 实现了引用计数。当 ImageView 开始显示图片时,计数 +1;ImageView 停止显示时,计数 -1。计数归零时,资源从 activeResources 中移除,降级进入 memoryCache。

第二层:LruResourceCache——基于 LRU 算法的内存缓存。

// com.bumptech.glide.load.engine.cache.LruResourceCache
public class LruResourceCache extends LruCache<Key, EngineResource<?>>
implements MemoryCache {
// 最大容量 = Application 可用内存的某个比例(默认 ~ 1/8)
}

第三层:磁盘缓存(Disk Cache)——DiskLruCache(基于 Jake Wharton 的 DiskLruCache 实现)。

// com.bumptech.glide.load.engine.DiskCacheStrategy 枚举
public enum DiskCacheStrategy {
ALL, // 缓存原始数据和变换后的资源
NONE, // 不缓存磁盘
DATA, // 只缓存原始数据(下载后的未解码数据)
RESOURCE, // 只缓存变换后的资源
AUTOMATIC, // 智能决策(通过 DataFetcher.getDataClass() 判断)
}

缓存命中流程:

请求加载图片
→ 查 activeResources(弱引用 Map) → 命中则返回
→ 查 memoryCache(LruResourceCache) → 命中则返回,进入 activeResources
→ 查 diskCache(ResourceCacheGenerator) → 命中则解码,进入 memoryCache + activeResources
→ 查 diskCache(DataCacheGenerator) → 命中则解码+变换,进入 memoryCache + activeResources
→ 从 Source 加载(网络/文件) → 解码+变换,写入 diskCache + memoryCache + activeResources

三、Bitmap 复用池:LruBitmapPool

Glide 的 Bitmap 复用池是一个独立的 LRU 池,专门管理可复用的 Bitmap 内存:

// com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
public class LruBitmapPool implements BitmapPool {
private final LruPoolStrategy strategy; // SizeConfigStrategy(Android 4.4+)/ AttributeStrategy

@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
// 复用 Bitmap:将其所有像素设为黑色/透明
result.eraseColor(Color.TRANSPARENT);
} else {
// 池中没有可复用的,创建新 Bitmap
result = Bitmap.createBitmap(width, height, config);
}
return result;
}

@Override
public void put(Bitmap bitmap) {
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize) {
bitmap.recycle(); // 不可复用或太大,直接回收
return;
}
strategy.put(bitmap);
}
}

SizeConfigStrategy 按 Size(宽每像素字节数)和 Config(ARGB_8888/RGB_565 等)将 Bitmap 分组。当请求一个 Bitmap 时,会在同 Size 级别的池中查找。

DecodeJob 中,Downsampler 解码图片时会尝试从 BitmapPool 获取可复用的 Bitmap:

// Downsampler.java 核心逻辑
Bitmap result = bitmapPool.getDirtyOrNull(outWidth, outHeight, expectedConfig);
if (result == null) {
result = Bitmap.createBitmap(outWidth, outHeight, expectedConfig);
}
// 使用 inBitmap 复用
options.inBitmap = result;
bitmap = BitmapFactory.decodeStream(is, null, options);

四、图片采样的 Downsampler

图片通常比 ImageView 大得多,需要采样缩小以节省内存。Glide 的 Downsampler 负责这项工作:

// com.bumptech.glide.load.resource.bitmap.Downsampler.java
public Resource<Bitmap> decode(InputStream is, int requestedWidth,
int requestedHeight, Options options, ...) throws IOException {

// 1. 读取图片头部信息(不加载整个图片,使用 inJustDecodeBounds)
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
int sourceWidth = options.outWidth;
int sourceHeight = options.outHeight;

// 2. 计算采样率
int sampleSize = getRoundedSampleSize(
calculateScaling(sourceWidth, sourceHeight, requestedWidth, requestedHeight));
options.inSampleSize = sampleSize;

// 3. 实际解码
options.inJustDecodeBounds = false;
// 4. 从 BitmapPool 获取可复用 Bitmap,设置 inBitmap
Bitmap result = bitmapPool.get(width, height, config);
options.inBitmap = result;

return BitmapResource.obtain(
BitmapFactory.decodeStream(is, null, options), bitmapPool);
}

inSampleSize 必须是 2 的幂次(1, 2, 4, 8…),Android 的解码器利用这个信息只解码排列中的某些像素点。

DownsampleStrategy 定义了四种采样策略:

// DownsampleStrategy 枚举
FIT_CENTER // 图片完整显示在 View 内(可能留白)
CENTER_INSIDE // 图片完整显示(不大于 View 且不小于原图)
CENTER_OUTSIDE // 图片填满 View(可能裁剪)
NONE // 不缩放,使用原图尺寸

对应不同的采样率计算逻辑。

五、生命周期绑定:RequestManagerFragment

Glide 通过注入一个无 UI 的 Fragment 来监听 Activity/Fragment 的生命周期:

// com.bumptech.glide.manager.RequestManagerRetriever.java
public RequestManager get(Activity activity) {
// 1. 如果 activity 在后台线程,使用 Application 的 RequestManager
android.app.FragmentManager fm = activity.getFragmentManager();
// 2. 查找现有的 SupportRequestManagerFragment
RequestManagerFragment current = getRequestManagerFragment(fm, ...);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(activity.getApplicationContext());
requestManager = factory.build(
glide, current.getLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}

// RequestManagerFragment.java
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(); }
}

当 Fragment 收到 onDestroy() 时,RequestManager 会调用 pauseRequests()clearRequests() 取消所有进行中和待处理的请求。这个设计使得开发者无需手动管理图片请求的生命周期。

六、Coil:Kotlin 协程的轻量级方案

Coil 是 Kotlin 生态的图片加载框架,架构上与 Glide 类似但更轻量(约 2000 个方法 vs Glide 的约 5000 个方法),完全基于 Kotlin 协程:

// Coil 的 ImageLoader 执行引擎
// coil-base/src/main/java/coil/RealImageLoader.kt
suspend fun execute(request: ImageRequest): ImageResult {
// 1. 检查内存缓存
// 2. 创建 fetcher(根据 data 类型:HttpFetcher、FileFetcher、AssetFetcher 等)
// 3. 使用 kotlinx.coroutines 协程执行加载、解码、变换
// 4. 缓存结果
}

Coil vs Glide 的主要差异:

  • Coil 使用 Kotlin 协程替代线程池,代码更简洁。
  • Coil 默认与 Lifecycle 集成,自动取消协程,无需额外配置。
  • Coil 支持 Compose(AsyncImage 组件),与 Jetpack Compose 无缝集成。
  • Coil 的 APK 体积更小(~200KB vs Glide 的 ~500KB+,不含 OkHttp)。

七、面试常问题目

Q1: Glide 的三层缓存架构是如何工作的?为什么要区分 activeResources 和 memoryCache?

三层缓存:activeResources(弱引用 Map,正在使用的资源)→ LruResourceCache(内存 LRU 缓存)→ DiskCache(磁盘缓存)。区分 activeResources 和 memoryCache 的原因:(1) activeResources 中的资源正在被使用,不能被 LRU 策略回收;(2) 使用弱引用,一旦没有强引用指向资源,会触发回调将资源移回 memoryCache;(3) LRU 池只管理”当前未使用但可能被再次使用”的资源。这是一种分层淘汰策略——正在用的永远留在内存,最近用过的次之,最老的被淘汰。

Q2: Glide 如何实现 Bitmap 的复用?

通过 LruBitmapPool 管理可复用的 Bitmap 对象。当新的 Bitmap 需要创建时,先从池中查找尺寸和配置匹配的 Bitmap(调用 bitmapPool.get()),如果找到则复用其内存(通过 BitmapFactory.Options.inBitmap 参数),只需在复用前擦除原有像素数据。当 Bitmap 不再使用时,调用 bitmapPool.put(bitmap) 放回池中。这避免了频繁的 malloc/native memory allocation 和 GC 压力。

Q3: Glide 的 RequestManagerFragment 是如何保证生命周期安全的?

Glide 通过 FragmentManager 注入一个无 UI 的 Fragment(RequestManagerFragment),利用 Fragment 的生命周期回调(onStart、onStop、onDestroy)来管理图片请求。当 Fragment 的 onStop 触发时,RequestManager 暂停该页面的图片请求(节省带宽);onDestroy 触发时,取消所有请求并释放资源。这个机制完全基于 Android Framework 的 Fragment 生命周期,无需依赖 androidx.lifecycle 包。

Q4: 为什么图片加载框架需要自定义 Downsampler 而不是直接用 BitmapFactory?

BitmapFactory 的原生采样(inSampleSize)只支持 2 的幂次采样(1, 2, 4, 8…),无法精确匹配 View 的尺寸。例如 ImageView 是 300dp(约 900px),图片是 4000x3000,最优的 inSampleSize 应该是 4(缩小到 1000px),剩余的缩放由变换(Transformation)完成。精确采样需要结合 inSampleSize 基数和后续的矩阵缩放(Matrix),这正是 Glide 的 Downsampler 所实现的。


参考源码路径:

  • Glide 仓库:https://github.com/bumptech/glide
  • Engine:library/src/main/java/com/bumptech/glide/load/engine/Engine.java
  • DecodeJob:library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java
  • LruResourceCache:library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java
  • LruBitmapPool:library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java
  • Downsampler:library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
  • RequestManagerRetriever:library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java
  • Coil 仓库:https://github.com/coil-kt/coil
打赏
  • 微信
  • 支付宝

评论