一、图片加载框架的核心问题 在 Android 上加载和显示图片,看似简单实则包含了一系列棘手的问题:
内存管理 :Bitmap 是 Android 中最占内存的对象。一张 1024x1024 的 ARGB_8888 图片占用 4MB,而屏幕通常只有 1080px 宽。如果不缩放,列表滑动时会迅速耗尽内存。
图片复用 :创建和回收 Bitmap 的代价很高,需要池化复用。
生命周期绑定 :Activity 销毁后,未完成的图片请求应被取消,否则会导致内存泄漏和崩溃。
缓存策略 :既要内存缓存(快速访问),又要磁盘缓存(持久化),还要在两者之间找到平衡。
格式适配 :不同的图片格式(PNG、JPEG、WebP、GIF、SVG、HEIF)需要不同的解码器。
变换处理 :图片加载后可能需要进行裁剪、圆角、模糊等变换。
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:缓存策略的核心 public class Engine implements EngineJobListener , MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { private final MemoryCache cache; 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); EngineResource<?> memoryResource = loadFromActiveResources(key, isMemoryCacheable); if (memoryResource != null ) { cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE); return null ; } EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null ) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); return null ; } 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 算法的内存缓存。
public class LruResourceCache extends LruCache <Key, EngineResource<?>> implements MemoryCache { }
第三层:磁盘缓存(Disk Cache) ——DiskLruCache(基于 Jake Wharton 的 DiskLruCache 实现)。
public enum DiskCacheStrategy { ALL, NONE, DATA, RESOURCE, AUTOMATIC, }
缓存命中流程:
请求加载图片 → 查 activeResources(弱引用 Map) → 命中则返回 → 查 memoryCache(LruResourceCache) → 命中则返回,进入 activeResources → 查 diskCache(ResourceCacheGenerator) → 命中则解码,进入 memoryCache + activeResources → 查 diskCache(DataCacheGenerator) → 命中则解码+变换,进入 memoryCache + activeResources → 从 Source 加载(网络/文件) → 解码+变换,写入 diskCache + memoryCache + activeResources
三、Bitmap 复用池:LruBitmapPool Glide 的 Bitmap 复用池是一个独立的 LRU 池,专门管理可复用的 Bitmap 内存:
public class LruBitmapPool implements BitmapPool { private final LruPoolStrategy strategy; @Override public Bitmap get (int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config); if (result != null ) { result.eraseColor(Color.TRANSPARENT); } else { 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:
Bitmap result = bitmapPool.getDirtyOrNull(outWidth, outHeight, expectedConfig);if (result == null ) { result = Bitmap.createBitmap(outWidth, outHeight, expectedConfig); } options.inBitmap = result; bitmap = BitmapFactory.decodeStream(is, null , options);
四、图片采样的 Downsampler 图片通常比 ImageView 大得多,需要采样缩小以节省内存。Glide 的 Downsampler 负责这项工作:
public Resource<Bitmap> decode (InputStream is, int requestedWidth, int requestedHeight, Options options, ...) throws IOException { options.inJustDecodeBounds = true ; BitmapFactory.decodeStream(is, null , options); int sourceWidth = options.outWidth; int sourceHeight = options.outHeight; int sampleSize = getRoundedSampleSize( calculateScaling(sourceWidth, sourceHeight, requestedWidth, requestedHeight)); options.inSampleSize = sampleSize; options.inJustDecodeBounds = false ; 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 定义了四种采样策略:
FIT_CENTER CENTER_INSIDE CENTER_OUTSIDE NONE
对应不同的采样率计算逻辑。
五、生命周期绑定:RequestManagerFragment Glide 通过注入一个无 UI 的 Fragment 来监听 Activity/Fragment 的生命周期:
public RequestManager get (Activity activity) { android.app.FragmentManager fm = activity.getFragmentManager(); 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; } 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 协程:
suspend fun execute (request: ImageRequest ) : ImageResult { }
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