一、图片加载框架的核心问题 在 Android 上加载和显示图片,看似简单实则包含了一系列棘手的问题:
内存管理 :Bitmap 是 Android 中最占内存的对象。一张 1024x1024 的 ARGB_8888 图片占用 4MB,而屏幕通常只有 1080px 宽。如果不缩放,列表滑动时会迅速耗尽内存。
图片复用 :创建和回收 Bitmap 的代价很高,需要池化复用——Bitmap 的像素数据存储在 native 堆中,Java 堆中的 Bitmap 对象只是一个壳。
生命周期绑定 :Activity 销毁后,未完成的图片请求应被取消,否则会导致内存泄漏和崩溃。
缓存策略 :既要内存缓存(快速访问),又要磁盘缓存(持久化),还要在两者之间找到平衡。
格式适配 :不同的图片格式(PNG、JPEG、WebP、GIF、SVG、HEIF)需要不同的解码器。
变换处理 :图片加载后可能需要进行裁剪、圆角、模糊等变换。
渐进式加载 :大图应先显示低分辨率预览,再逐步加载高分辨率版本。
Glide(https://github.com/bumptech/glide)是 Google 推荐的图片加载框架,被广泛部署在 Android 应用中。Coil(https://github.com/coil-kt/coil)是 Kotlin 生态的后起之秀。Fresco(https://github.com/facebook/fresco)专注于内存管理优化。
二、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 Glide 的注册表机制(Registry) Glide 使用注册表模式来管理各种类型的处理组件。核心在 com.bumptech.glide.Registry 类中:
registry .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader .Factory()) .append(File.class, InputStream.class, new FileLoader .StreamFactory()) .append(Uri.class, InputStream.class, new HttpUriLoader .Factory()) .append(InputStream.class, Bitmap.class, new StreamBitmapDecoder (downsampler, ...)) .append(ByteBuffer.class, Bitmap.class, new ByteBufferBitmapDecoder (downsampler)) .append(InputStream.class, GifDrawable.class, new StreamGifDecoder (registry.getImageHeaderParsers(), byteBufferGifDecoder, ...)) .append(Bitmap.class, new BitmapEncoder ()) .append(GifDrawable.class, new GifDrawableEncoder ()) .register(GifDrawable.class, Bitmap.class, new GifBitmapProvider (bitmapPool));
Registry 允许开发者注册自定义的 ModelLoader(如从加密文件加载)和 ResourceDecoder(如支持 HEIF 格式)。这是 Glide 可扩展性的核心。
2.2 ModelLoader 的工作机制 ModelLoader 负责将”模型”(如 URL、File、Uri)转换为”数据”(InputStream、ByteBuffer、ParcelFileDescriptor):
public interface ModelLoader <Model, Data> { LoadData<Data> buildLoadData (@NonNull Model model, int width, int height, @NonNull Options options) ; boolean handles (@NonNull Model model) ; class LoadData <Data> { public final Key sourceKey; public final List<Key> alternateKeys; public final DataFetcher<Data> fetcher; } } public interface DataFetcher <T> { void loadData (@NonNull Priority priority, @NonNull DataCallback<? super T> callback) ; void cleanup () ; void cancel () ; Class<T> getDataClass () ; DataSource getDataSource () ; }
每个 ModelLoader 返回一个 LoadData,其中包含 DataFetcher。Glide 通过 model 类型(如 GlideUrl)和期望的 data 类型(如 InputStream)来查找匹配的 ModelLoader。
2.3 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<?> current = jobs.get(key, onlyRetrieveFromCache); if (current != null ) { current.addCallback(cb, callbackExecutor); return new LoadStatus (cb, current); } 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); } }
Engine 的 jobs Map 实现了 加载去重 :当多个 ImageView 同时请求同一张图片时,只有一个 DecodeJob 真正执行加载,其他请求共享结果。这通过 EngineJob.addCallback() 实现——同一个 job 可以有多个 callback。
2.4 三层缓存架构 Glide 的内存缓存分为两层:
第一层:活跃资源(Active Resources) ——Map<Key, WeakReference<EngineResource<?>>>
这是当前正在被显示的图片。EngineResource 实现了引用计数。当 ImageView 开始显示图片时,计数 +1;ImageView 停止显示时,计数 -1。计数归零时,资源从 activeResources 中移除,降级进入 memoryCache。
引用计数的核心实现:
class EngineResource <Z> implements Resource <Z> { private int acquired; synchronized void acquire () { if (isRecycled) { throw new IllegalStateException ("Cannot acquire a recycled resource" ); } ++acquired; } synchronized void release () { if (acquired <= 0 ) { throw new IllegalStateException ("Cannot release a recycled or not yet acquired resource" ); } if (--acquired == 0 ) { listener.onResourceReleased(key, this ); } } }
第二层:LruResourceCache ——基于 LRU 算法的内存缓存。
public class LruResourceCache extends LruCache <Key, EngineResource<?>> implements MemoryCache { @Override protected int getSize (Key key, EngineResource<?> item) { return item.getSize(); } @Override protected void onItemEvicted (Key key, EngineResource<?> item) { item.recycle(); } }
第三层:磁盘缓存(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
为什么区分 activeResources 和 memoryCache?
这是一种分层淘汰策略。activeResources 中的资源正在被显示,不能被 LRU 策略回收——这可能导致正在显示的图片突然消失。LRU 池只管理”当前未使用但可能被再次使用”的资源。当不再显示时,引用计数归零,资源从 activeResources 降级到 memoryCache,等待 LRU 策略管理。
三、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 级别的池中查找。
SizeConfigStrategy 的分组逻辑 :
static final class Key implements Poolable { int width; int height; Bitmap.Config config; }
在 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);
inBitmap 的兼容性限制 :
Android 4.4 (API 19) 之前:inBitmap 只能复用尺寸完全相同的 Bitmap
Android 4.4+:inBitmap 可以复用任何尺寸不大于目标图片的 Bitmap(条件是 inSampleSize == 1,或者配置为 RGB_565 时可以复用任意配置)
四、图片采样的 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
对应不同的采样率计算逻辑。例如 CENTER_OUTSIDE 的采样率计算:
float widthPercentage = requestedWidth / (float ) sourceWidth;float heightPercentage = requestedHeight / (float ) sourceHeight;float scaleFactor = Math.max(widthPercentage, heightPercentage);
五、生命周期绑定:RequestManagerFragment Glide 通过注入一个无 UI 的 Fragment 来监听 Activity/Fragment 的生命周期:
public RequestManager get (Activity activity) { android.app.FragmentManager fm = activity.getFragmentManager(); RequestManagerFragment current = getRequestManagerFragment(fm, null ); 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 收到 onStop() 时,RequestManager 会调用 pauseRequests() 暂停图片加载(节省带宽);onDestroy() 时调用 clearRequests() 取消所有进行中和待处理的请求。这个设计使得开发者无需手动管理图片请求的生命周期。
5.1 在 Fragment 中的生命周期绑定 对于 Fragment 内部的 Glide 请求,Glide 会查找嵌套的 SupportRequestManagerFragment:
public RequestManager get (Fragment fragment) { FragmentManager fm = fragment.getChildFragmentManager(); return supportFragmentGet(fragment.getContext(), fm, null , isActivityVisible(fragment.getActivity())); }
确保子 Fragment 中的请求跟随 Fragment 的视图生命周期(在 onDestroyView 时 clear)。
Glide 的变换会生成新的缓存键,这意味着 url + centerCrop 和 url + circleCrop 是两个不同的缓存条目:
public T transform (@NonNull Transformation<Bitmap> transformation) { transformations.put(Bitmap.class, transformation); return self(); }
变换的实现基于 BitmapTransformation 抽象类:
public abstract class BitmapTransformation implements Transformation <Bitmap> { @Override public final Resource<Bitmap> transform ( @NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) { BitmapPool bitmapPool = Glide.get(context).getBitmapPool(); Bitmap toTransform = resource.get(); Bitmap outBitmap = transform(context.getApplicationContext(), bitmapPool, toTransform, outWidth, outHeight); if (!toTransform.equals(outBitmap)) { resource.recycle(); } return BitmapResource.obtain(outBitmap, bitmapPool); } protected abstract Bitmap transform ( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) ;}
CenterCrop 的实现 :
public class CenterCrop extends BitmapTransformation { @Override protected Bitmap transform (@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { float scale = Math.max( (float ) outWidth / toTransform.getWidth(), (float ) outHeight / toTransform.getHeight()); int scaledWidth = Math.round(scale * toTransform.getWidth()); int scaledHeight = Math.round(scale * toTransform.getHeight()); Bitmap result = pool.get(outWidth, outHeight, toTransform.getConfig()); Canvas canvas = new Canvas (result); canvas.drawBitmap(toTransform, null , new RectF ((outWidth - scaledWidth) / 2f , ...), null ); return result; } }
七、Coil:Kotlin 协程的轻量级方案 Coil 是 Kotlin 生态的图片加载框架,架构上与 Glide 类似但更轻量(约 2000 个方法 vs Glide 的约 5000 个方法),完全基于 Kotlin 协程:
suspend fun execute (request: ImageRequest ) : ImageResult { val memoryCacheKey = request.memoryCacheKey if (memoryCacheKey != null ) { val cached = memoryCacheService[memoryCacheKey] if (cached != null ) return cached } val fetcher = registry.getFetcher(request.data , request.options) val fetched = fetcher.fetch(request) val decoded = registry.decode(fetched, request.options) val transformed = registry.transform(decoded, request) memoryCacheService[memoryCacheKey] = transformed return transformed }
7.1 Coil vs Glide vs Fresco 综合对比
特性
Glide
Coil
Fresco
编程语言
Java / Kotlin
Kotlin
Java
异步方式
线程池 (ExecutorService)
协程 (Coroutines)
自定义线程池
方法数
~5000
~2000
~7000
APK 增量
~500KB
~200KB
~2MB
内存策略
BitmapPool + LRU 缓存
BitmapPool + LRU 缓存
Native 堆 + 自定义内存区域
GIF 支持
内置
需扩展 (coil-gif)
内置(WebP 动画也支持)
Compose 支持
accompanist-glide
原生 Coil (AsyncImage)
无官方 Compose 支持
生命周期集成
RequestManagerFragment
与 Lifecycle 协程天然集成
DraweeController
定制性
高(Registry 机制)
中
低(架构较封闭)
网络库耦合
需手动集成 OkHttp/Volley
默认 OkHttp
自带网络栈
Coil vs Glide 的主要差异:
Coil 使用 Kotlin 协程替代线程池,代码更简洁。
Coil 默认与 Lifecycle 集成,自动取消协程,无需额外配置。
Coil 支持 Compose(AsyncImage 组件),与 Jetpack Compose 无缝集成。
Coil 的 APK 体积更小(~200KB vs Glide 的 ~500KB+,不含 OkHttp)。
Glide 的 Registry 可扩展性更强——可以注册自定义的 ModelLoader 和 Decoder,支持更广泛的数据源和图片格式。
7.2 Fresco 的独特设计 Fresco 将 Bitmap 的像素数据存储在 ashmem(Android Shared Memory) 或 native heap 中,绕过 Java heap 的限制。在 Android 4.x 及更早版本中,这能有效避免 OOM。但随着 ART 的改进(Android 8+ Bitmap 像素数据默认存储在 native heap),Fresco 这个优势被大幅削弱。
八、面试常问题目 Q1: Glide 的三层缓存架构是如何工作的?为什么要区分 activeResources 和 memoryCache?
三层缓存:activeResources(弱引用 Map,正在使用的资源)→ LruResourceCache(内存 LRU 缓存)→ DiskCache(磁盘缓存)。区分 activeResources 和 memoryCache 的原因:(1) activeResources 中的资源正在被使用,不能被 LRU 策略回收;(2) 使用弱引用+引用计数,计数归零时资源自动从 activeResources 降级到 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 所实现的。
Q5: Glide 的 Registry 机制是如何实现可扩展性的?
Registry 通过四个核心接口实现可扩展性:ModelLoader(模型→数据)、ResourceDecoder(数据→资源)、ResourceEncoder(资源→磁盘缓存)、ResourceTranscoder(资源类型转换)。开发者可以注册自定义实现来支持新的数据源(如加密文件、自定义 URI scheme)和新的图片格式(如 HEIF、AVIF)。每个组件有对应的 Factory 接口,Glide 通过运行时注册表查找匹配的处理链。
参考源码路径:
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
Registry:library/src/main/java/com/bumptech/glide/Registry.java
ModelLoader:library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java
Coil 仓库:https://github.com/coil-kt/coil
Fresco 仓库:https://github.com/facebook/fresco