一、Glide 概述与整体架构 1.1 Glide 是什么 Glide 是 Google 推荐的 Android 图片加载库(由 bumptech 开发,Google 员工 Sam Judd 主创),被广泛应用于 Google 自家的应用(如 Google Photos、Google I/O 应用)。其名称来源于”滑行”——图片像在冰面上滑行一样流畅出现在屏幕上。
源码仓库 :github.com/bumptech/glide
核心能力 :
自动生命周期管理(Activity/Fragment 暂停时暂停加载,销毁时取消)
多级缓存(活动资源、内存缓存、磁盘缓存)
智能下采样(Downsampling),按 ImageView 实际尺寸加载
Bitmap 池复用,减少 GC 压力
GIF/动画 WebP 支持
可定制的转换管线(圆形裁剪、模糊、着色等)
请求协调(缩略图、错误图、占位图)
1.2 整体架构 Glide.with(activity) .load(url) .override(300, 300) .into(imageView)
这一行代码背后,是 Glide 精心设计的一系列组件协作。整体架构如下:
Glide (单例) │ ├─ RequestManager (每个 Activity/Fragment 一个) │ ├─ lifecycle 管理 (RequestManagerFragment) │ ├─ RequestTracker (追踪所有请求,生命周期匹配时暂停/恢复) │ └─ RequestBuilder (构建请求参数) │ ├─ Engine (全局单例,加载引擎) │ ├─ MemoryCache (LruResourceCache) │ ├─ ActiveResources (弱引用 Map,正在使用的 Bitmap) │ ├─ DiskCache (DiskLruCache) │ ├─ EngineJob (负责异步加载 + 编解码) │ └─ DecodeJob (负责从磁盘/网络获取 + 解码 + 变换) │ ├─ ModelLoader (数据源加载器) │ ├─ HttpGlideUrlLoader (网络) │ ├─ FileLoader (本地文件) │ ├─ AssetUriLoader (asset) │ ├─ ResourceUriLoader (drawable) │ └─ 自定义 (加密文件等) │ ├─ DecodePath (解码管线) │ ├─ ResourceDecoder (解码: InputStream → Bitmap/Drawable) │ └─ ResourceEncoder (编码: Bitmap → OutputStream,用于磁盘缓存) │ └─ Transformation (变换) ├─ CenterCrop ├─ CircleCrop ├─ RoundedCorners └─ MultiTransformation
1.3 一张图的完整旅程 以 Glide.with(activity).load("https://example.com/photo.jpg").into(imageView) 为例:
1. with(activity) └─ 创建/获取 RequestManager └─ 注入 RequestManagerFragment,绑定 Activity 生命周期 2. load(url) └─ 创建 RequestBuilder └─ 保存 url 作为 model 3. apply(options) └─ 设置 RequestOptions(占位图、缓存策略、变换、尺寸等) 4. into(imageView) └─ 构建 SingleRequest └─ 提交给 RequestTracker └─ Engine.load() ┌─ 查 ActiveResources(弱引用 Map) ├─ 查 MemoryCache(LruResourceCache) ├─ 查 DiskCache(DiskLruCache ResourceCacheGenerator) ├─ 查 DiskCache 原数据(DataCacheGenerator) └─ 网络加载(SourceGenerator + HttpUrlFetcher) └─ 解码(Downsampler)→ 变换(Transformation)→ 回调 └─ 写入 ActiveResources → 显示到 ImageView
二、生命周期管理 —— Glide 的独门绝技 2.1 RequestManagerFragment Glide 生命周期管理的核心是一个无 UI 的 Fragment ,注入到宿主 Activity 中:
源码位置 :com/bumptech/glide/manager/RequestManagerFragment.java
public class RequestManagerFragment extends Fragment { private final ActivityFragmentLifecycle lifecycle; private RequestManager requestManager; public RequestManagerFragment () { this (new ActivityFragmentLifecycle ()); } @Override public void onStart () { super .onStart(); lifecycle.onStart(); } @Override public void onStop () { super .onStop(); lifecycle.onStop(); } @Override public void onDestroy () { super .onDestroy(); lifecycle.onDestroy(); requestManager = null ; } }
关键设计 :
onStart → 恢复所有暂停的请求
onStop → 暂停所有进行中的请求(减少不必要的 CPU 和网络消耗)
onDestroy → 取消所有请求,防止内存泄漏
2.2 Glide.with() 的查找逻辑 源码位置 :com/bumptech/glide/Glide.java → RequestManagerRetriever.java
public static RequestManager with (@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity); } public RequestManager get (@NonNull FragmentActivity activity) { if (activity.isDestroyed()) { throw new IllegalArgumentException ("Activity is destroyed" ); } FrameLayout.LayoutParams params = new FrameLayout .LayoutParams(1 , 1 ); FragmentManager fm = activity.getSupportFragmentManager(); RequestManagerFragment fragment = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (fragment == null ) { fragment = new RequestManagerFragment (); fm.beginTransaction() .add(fragment, FRAGMENT_TAG) .commitAllowingStateLoss(); fm.executePendingTransactions(); } return fragment.getRequestManager(); }
关键点 :
每个 Activity 只创建一个 RequestManagerFragment
Fragment 的 tag 是固定常量,全局唯一
commitAllowingStateLoss() 避免因 onSaveInstanceState 后提交而崩溃
executePendingTransactions() 确保 Fragment 立即添加到 Activity
2.3 ActivityFragmentLifecycle 源码位置 :com/bumptech/glide/manager/ActivityFragmentLifecycle.java
class ActivityFragmentLifecycle implements Lifecycle { private final Set<LifecycleListener> lifecycleListeners = Collections.newSetFromMap(new WeakHashMap <LifecycleListener, Boolean>()); private boolean isStarted; private boolean isDestroyed; @Override public void addListener (@NonNull LifecycleListener listener) { lifecycleListeners.add(listener); if (isDestroyed) { listener.onDestroy(); } else if (isStarted) { listener.onStart(); } else { listener.onStop(); } } void onStart () { isStarted = true ; for (LifecycleListener listener : lifecycleListeners) { listener.onStart(); } } void onStop () { isStarted = false ; for (LifecycleListener listener : lifecycleListeners) { listener.onStop(); } } void onDestroy () { isDestroyed = true ; for (LifecycleListener listener : lifecycleListeners) { listener.onDestroy(); } } }
设计精妙之处 :
使用 WeakHashMap 存储 listeners,避免 listener 注册后遗忘导致的泄漏
addListener 时根据当前状态立即回调,保证状态同步
RequestTracker 实现了 LifecycleListener,在生命周期回调中控制请求的 pause/resume/clear
2.4 RequestTracker —— 请求生命周期管理 源码位置 :com/bumptech/glide/manager/RequestTracker.java
public class RequestTracker { private final Set<Request> requests = Collections.newSetFromMap(new WeakHashMap <Request, Boolean>()); private final List<Request> pendingRequests = new ArrayList <>(); private boolean isPaused; public void runRequest (@NonNull Request request) { requests.add(request); if (!isPaused) { request.begin(); } else { pendingRequests.add(request); } } public void pauseRequests () { isPaused = true ; for (Request request : requests) { if (request.isRunning()) { request.pause(); pendingRequests.add(request); } } } public void resumeRequests () { isPaused = false ; for (Request request : pendingRequests) { if (!request.isComplete() && !request.isCancelled()) { request.begin(); } } pendingRequests.clear(); } public void clearRequests () { for (Request request : requests) { request.clear(); } pendingRequests.clear(); } }
状态机 :
┌──────────────┐ │ Created │ └──────┬───────┘ │ runRequest() ┌──────▼───────┐ pauseRequests() ┌──────────────┐ │ Running │ ─────────────────> │ Paused │ └──────┬───────┘ <───────────────── └──────┬───────┘ │ resumeRequests() │ │ clearRequests() │ clearRequests() ┌──────▼───────┐ ┌──────▼───────┐ │ Cleared │ │ Cleared │ └──────────────┘ └──────────────┘
三、内存缓存设计 3.1 两级内存缓存架构 Glide 的内存缓存分为两层,这是它与 Fresco 和 Picasso 的重要差异:
┌─────────────────────────────────────┐ │ ActiveResources (弱引用 Map) │ │ 正在使用的 Bitmap │ │ Key = EngineResource 的弱引用 │ │ 作用:防止正在显示的 Bitmap 被回收 │ └──────────────┬──────────────────────┘ │ 引用计数降为 0 时迁移 ┌──────────────▼──────────────────────┐ │ LruResourceCache (LRU 内存缓存) │ │ 已不显示但可能重新使用的 Bitmap │ │ 基于 LruCache<Key, EngineResource> │ │ 作用:减少重复解码 │ └─────────────────────────────────────┘
为什么是两层?
如果只有 LRU 缓存:正在显示的图片如果被 LRU 淘汰,会导致崩溃或闪烁
如果只有弱引用:GIF/大图很容易在下次 GC 时被回收,缓存命中率低
两层结合:正在使用的拿弱引用保护;用过的移入 LRU,还能从磁盘恢复
3.2 ActiveResources 源码位置 :com/bumptech/glide/load/engine/ActiveResources.java
final class ActiveResources { final Map<Key, ResourceWeakReference> activeEngineResources = new ArrayMap <>(); private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue <>(); private final Handler mainHandler = new Handler (Looper.getMainLooper()); void activate (Key key, EngineResource<?> resource) { ResourceWeakReference toPut = new ResourceWeakReference ( key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed); ResourceWeakReference removed = activeEngineResources.put(key, toPut); if (removed != null ) { removed.reset(); } } void deactivate (Key key) { ResourceWeakReference removed = activeEngineResources.remove(key); if (removed != null ) { removed.reset(); } } Reference<EngineResource<?>> poll() { return resourceReferenceQueue.poll(); } static final class ResourceWeakReference extends WeakReference <EngineResource<?>> { final Key key; final boolean isActiveResourceRetentionAllowed; ResourceWeakReference(Key key, EngineResource<?> referent, ReferenceQueue<? super EngineResource<?>> queue, boolean isActiveResourceRetentionAllowed) { super (referent, queue); this .key = key; this .isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed; } } }
工作机制 :
图片解码完成 → acquire() 使引用计数 +1 → 加入 ActiveResources
图片显示到 ImageView → acquire() 再次 +1
当图片不再显示(View 回收、页面切换)→ release() 使引用计数 -1
引用计数降到 0 → 从 ActiveResources 移除 → 移入 LruResourceCache
如果 GC 从 ActiveResources 回收了 Bitmap → 通过 ReferenceQueue 发现 → 释放资源
3.3 EngineResource 引用计数 源码位置 :com/bumptech/glide/load/engine/EngineResource.java
class EngineResource <Z> implements Resource <Z> { private final Resource<Z> resource; private int acquired; private boolean isRecycled; private Key cacheKey; private boolean isMemoryCacheable; private ResourceListener listener; void acquire () { if (isRecycled) { throw new IllegalStateException ("Cannot acquire a recycled resource" ); } if (Looper.getMainLooper().equals(Looper.myLooper())) { ++acquired; } else { throw new IllegalThreadStateException ("Must call acquire on the main thread" ); } } void release () { if (acquired <= 0 ) { throw new IllegalStateException ("Cannot release a recycled or not yet acquired resource" ); } if (Looper.getMainLooper().equals(Looper.myLooper())) { if (--acquired == 0 ) { listener.onResourceReleased(cacheKey, this ); } } } void recycle () { if (acquired > 0 ) { throw new IllegalStateException ("Cannot recycle a resource while it is still acquired" ); } if (!isRecycled) { isRecycled = true ; resource.recycle(); } } }
引用计数变化过程 :
1. DecodeJob 解码完成 → acquire() → count=1, 加入 ActiveResources 2. ImageViewTarget.setResource() → acquire() → count=2 3. ImageView 被回收 → release() → count=1 4. Activity onStop → release() → count=0 → from ActiveResources remove → put into LruResourceCache 5. LruResourceCache 淘汰 → recycle() → Bitmap 回收到 BitmapPool
3.4 LruResourceCache —— LRU 驱逐策略 源码位置 :com/bumptech/glide/load/engine/cache/LruResourceCache.java
public class LruResourceCache extends LruCache <Key, EngineResource<?>> implements MemoryCache { private ResourceRemovedListener listener; @Override protected void entryRemoved (boolean evicted, Key key, EngineResource<?> oldValue, EngineResource<?> newValue) { if (listener != null ) { listener.onResourceRemoved(oldValue); } oldValue.recycle(); } @Override protected int getSize (EngineResource<?> item) { return item.getSize(); } }
继承自 Android SDK 的 android.util.LruCache:
protected void trimToSize (int maxSize) { while (true ) { K key; V value; synchronized (this ) { if (size < 0 || (map.isEmpty() && size != 0 )) { throw new IllegalStateException (...); } if (size <= maxSize) { break ; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true , key, value, null ); } }
3.5 Engine 中的缓存查询流程 源码位置 :com/bumptech/glide/load/engine/Engine.java
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<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null ) { cb.onResourceReady(active, 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(...); DecodeJob<R> decodeJob = decodeJobFactory.build(...); jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); engineJob.start(decodeJob); return new LoadStatus (cb, engineJob); }
EngineKey 的构成 :
class EngineKey implements Key { private final Object model; private final int width; private final int height; private final Class<?> resourceClass; private final Class<?> transcodeClass; private final Key signature; private final Map<Class<?>, Transformation<?>> transformations; private final Options options; private final Key originalKey; }
不同的 width/height/transformation/option 组合会产生不同的缓存 Key,这意味着同一个 URL 可以在不同 ImageView 中以不同尺寸缓存。
四、磁盘缓存设计 4.1 DiskLruCacheWrapper Glide 的磁盘缓存基于 Jake Wharton 的 DiskLruCache(AOSP libcore/luni/src/main/java/libcore/io/DiskLruCache.java 也有类似实现):
源码位置 :com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java
public class DiskLruCacheWrapper implements DiskCache { private static final int APP_VERSION = 1 ; private static final int VALUE_COUNT = 1 ; private DiskLruCache diskLruCache; @Override public File get (Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); DiskLruCache.Value value = diskLruCache.get(safeKey); return value != null ? value.getFile(0 ) : null ; } @Override public void put (Key key, Writer writer) { String safeKey = safeKeyGenerator.getSafeKey(key); DiskLruCache.Editor editor = diskLruCache.edit(safeKey); if (editor != null ) { try { File file = editor.getFile(0 ); if (writer.write(file)) { editor.commit(); } } finally { editor.abortUnlessCommitted(); } } } void delete (Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); diskLruCache.remove(safeKey); } }
4.2 SafeKeyGenerator —— 缓存 Key 的安全化 URL 中可能包含非法文件名字符(如 ?, &, /, :),因此需要转换为安全的文件名:
源码位置 :com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java
public class SafeKeyGenerator { private final LruCache<Key, String> loadIdToSafeHash = new LruCache <>(1000 ); public String getSafeKey (Key key) { String safeKey; synchronized (loadIdToSafeHash) { safeKey = loadIdToSafeHash.get(key); } if (safeKey == null ) { safeKey = calculateHexStringDigest(key); } synchronized (loadIdToSafeHash) { loadIdToSafeHash.put(key, safeKey); } return safeKey; } private String calculateHexStringDigest (Key key) { try { MessageDigest md = MessageDigest.getInstance("SHA-256" ); key.updateDiskCacheKey(md); return Util.sha256BytesToHex(md.digest()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException (e); } } }
URL → SHA-256 → 64 字符的十六进制字符串 → 安全的文件名。
4.3 两种磁盘缓存策略 源码位置 :com/bumptech/glide/load/engine/DecodeJob.java
DecodeJob 在磁盘缓存阶段使用两种 Generator:
DataCacheGenerator (原数据缓存) └─ 缓存原始下载数据(未解码的 InputStream/ByteBuffer) └─ 优点:可以根据不同需求重新解码为不同尺寸/格式 └─ 缺点:解码有 CPU 开销 ResourceCacheGenerator (解码后缓存) └─ 缓存经过解码 + 变换后的 Bitmap(已确定尺寸/格式) └─ 优点:直接使用,零 CPU 解码开销 └─ 缺点:只能用于同一尺寸的 ImageView
DiskCacheStrategy 的四种模式 :
public enum DiskCacheStrategy { ALL(true , true ), NONE(false , false ), RESOURCE(false , true ), DATA(true , false ), AUTOMATIC; }
CacheStrategy 的决定逻辑 (在 DecodeJob.getNextStage() 中):
private Stage getNextStage (Stage current) { switch (current) { case INITIALIZE: return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); case DATA_CACHE: return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE; case SOURCE: return Stage.FINISHED; default : throw new IllegalArgumentException ("Unrecognized stage: " + current); } }
执行顺序:RESOURCE_CACHE → DATA_CACHE → SOURCE → FINISHED
五、Bitmap 池复用机制 5.1 LruBitmapPool 源码位置 :com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java
Glide 的 Bitmap 池解决了 Android 中 Bitmap 频繁创建/销毁导致的 GC 问题。核心数据结构是 GroupedLinkedMap:
public class LruBitmapPool implements BitmapPool { private static final String TAG = "LruBitmapPool" ; private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888; private final LruPoolStrategy strategy; private final Set<Bitmap.Config> allowedConfigs; private final int initialMaxSize; private final BitmapTracker tracker; private int maxSize; private int currentSize; private int hits; private int misses; private int puts; private int evictions; static { Set<Bitmap.Config> defaultAllowedConfigs = new HashSet <>(); defaultAllowedConfigs.addAll(Arrays.asList(Bitmap.Config.values())); if (Build.VERSION.SDK_INT >= 26 ) { defaultAllowedConfigs.add(Bitmap.Config.HARDWARE); } } }
5.2 LruPoolStrategy —— 两种匹配策略 SizeConfigStrategy (用于 Android 4.4+):
以尺寸 + 配置(Bitmap.Config)作为分组 Key:
class SizeConfigStrategy implements LruPoolStrategy { private static final int BYTES_PER_PIXEL_ARGB_8888 = 4 ; private static final int BYTES_PER_PIXEL_RGB_565 = 2 ; private final KeyPool keyPool = new KeyPool (); private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap <>(); private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap <>(); static class Key implements Poolable { private int size; private Bitmap.Config config; @Override public boolean equals (Object o) { if (o instanceof Key) { Key other = (Key) o; return size == other.size && Util.bothNullOrEqual(config, other.config); } return false ; } } }
查找逻辑 :
@Override @Nullable public Bitmap get (int width, int height, Bitmap.Config config) { int size = Util.getBitmapByteSize(width, height, config); Key targetKey = keyPool.get(size, config); Key bestKey = findBestKey(targetKey, size, config); if (bestKey == null ) { return null ; } Bitmap removed = groupedMap.removeLast(bestKey); if (removed != null ) { currentSize -= Util.getBitmapByteSize(removed); if (removed.getWidth() == width && removed.getHeight() == height && removed.getConfig() == config) { } else { removed.reconfigure(width, height, config); } } return removed; }
5.3 什么情况下不进入池 @Override public synchronized void put (Bitmap bitmap) { if (bitmap == null ) throw new NullPointerException ("Bitmap must not be null" ); if (bitmap.isRecycled()) throw new IllegalStateException ("Cannot pool recycled bitmap" ); if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || !allowedConfigs.contains(bitmap.getConfig())) { bitmap.recycle(); return ; } int size = strategy.getSize(bitmap); strategy.put(bitmap); tracker.add(bitmap); puts++; currentSize += size; trimToSize(maxSize); }
不会入池的情况 :
Bitmap 不可变(isMutable() == false)
Bitmap 尺寸超过池的最大容量(如超大图 > 屏幕尺寸 * 4)
Bitmap.Config 不在允许列表中(如 HARDWARE config 在某些设备上不兼容)
Bitmap 已经被回收(isRecycled() == true)
Android API 19+ 引入的 Bitmap.reconfigure() 允许修改已有 Bitmap 的尺寸和配置,而不需要重新分配内存:
public void reconfigure (int width, int height, Config config) { checkRecycled("Can't call reconfigure() on a recycled bitmap" ); if (width <= 0 || height <= 0 ) { throw new IllegalArgumentException (...); } if (!isMutable()) { throw new IllegalStateException ("only mutable bitmaps may be reconfigured" ); } if (getAllocationByteCount() < width * height * bytesPerPixel(config)) { throw new IllegalStateException (...); } nativeReconfigure(mNativePtr, width, height, config, getLayoutDirection() == LAYOUT_DIRECTION_RTL); mWidth = width; mHeight = height; mConfig = config; }
Glide 在 SizeConfigStrategy 中利用此机制:找一个总字节数 >= 需求的 Bitmap,reconfigure 为精确尺寸。
六、下采样(Downsampling) 6.1 Downsampler —— 核心解码器 源码位置 :com/bumptech/glide/load/resource/bitmap/Downsampler.java
Downsampler 是 Glide 防止 OOM 的核心组件。它根据目标 ImageView 的尺寸,计算合适的采样率(inSampleSize),确保解码出的 Bitmap 不大于所需:
public class Downsampler { public static final Downsampler AT_LEAST = new Downsampler (); public static final Downsampler CENTER_INSIDE = new Downsampler (); public static final Downsampler CENTER_OUTSIDE = new Downsampler (); private static final int MARK_POSITION = 20 * 1024 * 1024 ; private static final int MAX_DIMENSION = 16384 ; public Resource<Bitmap> decode (InputStream is, int outWidth, int outHeight, Options options, DecodeCallbacks callbacks) throws IOException { if (is instanceof RecyclableBufferedInputStream) { return decodeStream((RecyclableBufferedInputStream) is, options, callbacks); } else if (isBitmapRegionDecoderCompatible(options)) { return decodeBitmapRegionDecoder(is, options, callbacks); } else { return decodeStream(is, options, callbacks); } } }
6.2 计算 inSampleSize private static int calculateScaling (int inWidth, int inHeight, int outWidth, int outHeight, Scaling scaling) { if (scaling == Scaling.SAMPLE) { return Math.max( integerDivision(inWidth, outWidth), integerDivision(inHeight, outHeight) ); } return Math.min( integerDivision(inWidth, outWidth), integerDivision(inHeight, outHeight) ); } private static int integerDivision (int dividend, int divisor) { int result = dividend / divisor; return (dividend % divisor == 0 ) ? result : result + 1 ; }
inSampleSize 必须是 2 的幂 :Android BitmapFactory 虽然文档上说 inSampleSize 会向下取最接近的 2 的幂,但 Glide 主动做了取整:
private static int roundToPowerOfTwo (int sampleSize) { int result = 1 ; while (result < sampleSize) { result <<= 1 ; } return result; } int sampleSize = calculateScaling(inWidth, inHeight, outWidth, outHeight, scaling);sampleSize = roundToPowerOfTwo(sampleSize);
6.3 BitmapRegionDecoder —— 区域解码(超大图) 对于超大图(如 20000x10000 的全景图),直接解码为 Bitmap 会 OOM。Glide 使用 Android 的 BitmapRegionDecoder 进行区域解码 :
private Resource<Bitmap> decodeBitmapRegionDecoder (InputStream is, Options options, DecodeCallbacks callbacks) throws IOException { BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false ); int sourceWidth = decoder.getWidth(); int sourceHeight = decoder.getHeight(); BitmapRegionDecoderOptions regionOptions = new BitmapRegionDecoderOptions (); Rect region = new Rect (0 , 0 , sourceWidth, sourceHeight); int sampleSize = Math.max( sourceWidth / targetWidth, sourceHeight / targetHeight ); sampleSize = Math.max(1 , Integer.highestOneBit(sampleSize)); regionOptions.inSampleSize = sampleSize; regionOptions.inPreferredConfig = options.get(BitmapOptions.PREFERRED_CONFIG); Bitmap result = decoder.decodeRegion(region, regionOptions); decoder.recycle(); return BitmapResource.obtain(result, bitmapPool); }
6.4 硬件 Bitmap(Android O+) Android 8.0 引入了 Bitmap.Config.HARDWARE,Bitmap 的像素数据存储在 GPU 纹理内存中,而非 Java 堆内存:
Glide.with(context) .load(url) .set(DecodeFormat.PREFER_ARGB_8888) .into(imageView);
源码位置 :com/bumptech/glide/load/Transformation.java
public interface Transformation <T> extends Key { @NonNull Resource<T> transform (@NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight) ;}
Glide 内置的变换:
CenterCrop:等比缩放,居中裁剪
CenterInside:等比缩放,完全展示
FitCenter:等比缩放,适配目标区域
CircleCrop:圆形裁剪
RoundedCorners:圆角裁剪
GrayscaleTransformation:灰度化
BlurTransformation:模糊
public abstract class BitmapTransformation implements Transformation <Bitmap> { @Override public final Resource<Bitmap> transform (@NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) { if (outWidth <= 0 || outHeight <= 0 ) { throw new IllegalArgumentException ("Cannot transform with outWidth or" + " outHeight <= 0; outWidth=" + outWidth + ", outHeight=" + outHeight); } BitmapPool bitmapPool = Glide.get(context).getBitmapPool(); Bitmap toTransform = resource.get(); Bitmap transformed = transform(bitmapPool, toTransform, outWidth, outHeight); final Resource<Bitmap> result; if (toTransform.equals(transformed)) { result = resource; } else { result = BitmapResource.obtain(transformed, bitmapPool); } return result; } protected abstract Bitmap transform (@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) ;}
7.3 CenterCrop 实现 public class CenterCrop extends BitmapTransformation { private static final String ID = "com.bumptech.glide.load.resource.bitmap.CenterCrop" ; @Override protected Bitmap transform (@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { final Bitmap.Config config = toTransform.getConfig() != null ? toTransform.getConfig() : Bitmap.Config.ARGB_8888; Bitmap toReuse = pool.get(outWidth, outHeight, config); Bitmap transformed = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight); if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { toReuse.recycle(); } return transformed; } } public static Bitmap centerCrop (BitmapPool pool, Bitmap inBitmap, int width, int height) { float scale = Math.max( (float ) width / inBitmap.getWidth(), (float ) height / inBitmap.getHeight() ); float scaledWidth = scale * inBitmap.getWidth(); float scaledHeight = scale * inBitmap.getHeight(); int left = (int ) ((scaledWidth - width) / 2f ); int top = (int ) ((scaledHeight - height) / 2f ); Bitmap result = pool.get(width, height, inBitmap.getConfig()); if (result == null ) { result = Bitmap.createBitmap(width, height, inBitmap.getConfig()); } Canvas canvas = new Canvas (result); Matrix matrix = new Matrix (); matrix.setScale(scale, scale); matrix.postTranslate(-left, -top); canvas.drawBitmap(inBitmap, matrix, new Paint (Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG)); return result; }
public class MultiTransformation <T> implements Transformation <T> { private final Collection<Transformation<T>> transformations; @SafeVarargs public MultiTransformation (@NonNull Transformation<T>... transformations) { this .transformations = Arrays.asList(transformations); } @Override public Resource<T> transform (@NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight) { Resource<T> previous = resource; for (Transformation<T> transformation : transformations) { Resource<T> transformed = transformation.transform(context, previous, outWidth, outHeight); if (transformed != previous && previous != resource) { previous.recycle(); } previous = transformed; } return previous; } } Glide.with(context) .load(url) .transform(new MultiTransformation <>( new CenterCrop (), new RoundedCorners (16 ) )) .into(imageView);
八、GIF 解码 8.1 GifDecoder 架构 Glide 使用自定义的 GIF 解码器而非 Android 的 Movie 类,以获得更好的内存控制和帧管理:
源码位置 :com/bumptech/glide/gifdecoder/GifDecoder.java
public class GifDecoder { static final int STATUS_OK = 0 ; static final int STATUS_FORMAT_ERROR = 1 ; static final int STATUS_OPEN_ERROR = 2 ; static final int STATUS_PARTIAL_DECODE = 3 ; static final int STATUS_TOTAL_DECODE = 4 ; static final int EXTENSION_BLOCK_LABEL = 0x21 ; static final int GRAPHIC_CONTROL_EXTENSION = 0xF9 ; static final int APPLICATION_EXTENSION = 0xFF ; static final int PLAIN_TEXT_EXTENSION = 0x01 ; static final int COMMENT_EXTENSION = 0xFE ; static final int DISPOSAL_UNSPECIFIED = 0 ; static final int DISPOSAL_NONE = 1 ; static final int DISPOSAL_BACKGROUND = 2 ; static final int DISPOSAL_PREVIOUS = 3 ; private int [] act; private int bgColor; private int bgIndex; private int frameIndex; private int frameCount; private List<GifFrame> frames; private GifHeader header; private boolean isDecoded; public synchronized int read (@NonNull byte [] data) { this .data = data; this .header = new GifHeader (); frames = new ArrayList <>(); return parseData(); } private int parseData () { if (data == null ) { return STATUS_OPEN_ERROR; } int status = parseHeader(); if (status != STATUS_OK) { return status; } return parseContents(); } }
8.2 GIF 在 RecyclerView 中的处理 RecyclerView 快速滚动时,Glide 自动暂停 GIF 动画以减少 CPU 消耗:
class GifDrawable extends Drawable implements GifFrameLoader .FrameCallback, Animatable { @Override public void start () { isStarted = true ; frameLoader.subscribe(this ); invalidateSelf(); } @Override public void stop () { isStarted = false ; frameLoader.unsubscribe(this ); } @Override public boolean setVisible (boolean visible, boolean restart) { boolean changed = super .setVisible(visible, restart); if (visible) { start(); } else { stop(); } return changed; } }
当 ImageView 滚动出屏幕时(setVisible(false)),动画自动停止;滚动回来时恢复播放。
8.3 GIF 帧的内存管理 class GifFrameLoader { private final BitmapPool bitmapPool; private Bitmap currentFrame; private Bitmap nextFrame; private Bitmap pendingFrame; void onFrameReady (DelayDecodeFrame frame) { if (isRunning) { if (currentFrame != null && !bitmapPool.put(currentFrame)) { currentFrame.recycle(); } currentFrame = nextFrame; nextFrame = frame.frame; callback.onFrameReady(); } } }
九、请求协调 9.1 缩略图(Thumbnail) Glide 支持同时加载低分辨率缩略图和高清图:
Glide.with(context) .load(highResUrl) .thumbnail(0.1f ) .into(imageView); Glide.with(context) .load(highResUrl) .thumbnail(Glide.with(context).load(lowResUrl)) .into(imageView);
源码机制 :
public RequestBuilder<TranscodeType> thumbnail (@Nullable RequestBuilder<TranscodeType> thumb) { this .thumbBuilder = thumb; return this ; } public void begin () { if (thumbBuilder != null ) { thumbBuilder.begin(); } status = Status.RUNNING; }
两个请求同时执行;缩略图通常更小、更快,先展示;完整图加载完成后自动替换。
9.2 占位图与错误图 Glide.with(context) .load(url) .placeholder(R.drawable.ic_placeholder) .error(R.drawable.ic_error) .fallback(R.drawable.ic_fallback) .into(imageView);
占位图的状态变更 (SingleRequest.onResourceReady()):
private void onResourceReady (Resource<R> resource, R result, DataSource dataSource) { if (status == Status.CANCELLED || status == Status.CLEARED) { resource.recycle(); return ; } if (status == Status.WAITING_FOR_SIZE) { this .resource = resource; this .result = result; return ; } target.onResourceReady(result, animation); if (placeholderDrawable != null ) { setErrorPlaceholder(); } status = Status.COMPLETE; }
9.3 请求去重 Engine.load() 中的 jobs Map 确保同一个 Key 的请求不会重复执行:
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); if (current != null ) { current.addCallback(cb, callbackExecutor); return new LoadStatus (cb, current); }
如果同一个 URL 在多个 ImageView 中加载,且尺寸、变换参数都相同,只有一个 DecodeJob 真正执行,结果通过多个 callback 分发给所有请求者。
十、自定义 ModelLoader 10.1 从加密文件加载图片 这是一个生产级别的自定义 ModelLoader 示例,用于从加密文件加载图片:
@GlideModule public class EncryptedImageModel { private final File encryptedFile; private final SecretKey secretKey; public EncryptedImageModel (File encryptedFile, SecretKey secretKey) { this .encryptedFile = encryptedFile; this .secretKey = secretKey; } public File getEncryptedFile () { return encryptedFile; } public SecretKey getSecretKey () { return secretKey; } } public class EncryptedImageModelLoaderFactory implements ModelLoaderFactory <EncryptedImageModel, InputStream> { @Override public ModelLoader<EncryptedImageModel, InputStream> build ( @NonNull MultiModelLoaderFactory multiFactory) { return new EncryptedImageModelLoader (); } @Override public void teardown () {} } public class EncryptedImageModelLoader implements ModelLoader <EncryptedImageModel, InputStream> { @Override public LoadData<InputStream> buildLoadData (@NonNull EncryptedImageModel model, int width, int height, @NonNull Options options) { return new LoadData <>( new ObjectKey (model.getEncryptedFile().getAbsolutePath()), new EncryptedFileFetcher (model) ); } @Override public boolean handles (@NonNull EncryptedImageModel model) { return true ; } } public class EncryptedFileFetcher implements DataFetcher <InputStream> { private final EncryptedImageModel model; private InputStream stream; public EncryptedFileFetcher (EncryptedImageModel model) { this .model = model; } @Override public void loadData (@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding" ); cipher.init(Cipher.DECRYPT_MODE, model.getSecretKey(), new GCMParameterSpec (128 , readIv())); CipherInputStream cis = new CipherInputStream ( new FileInputStream (model.getEncryptedFile()), cipher); this .stream = cis; callback.onDataReady(cis); } catch (Exception e) { callback.onLoadFailed(e); } } @Override public void cleanup () { if (stream != null ) { try { stream.close(); } catch (IOException ignored) {} } } @Override public void cancel () { cleanup(); } @NonNull @Override public Class<InputStream> getDataClass () { return InputStream.class; } @NonNull @Override public DataSource getDataSource () { return DataSource.LOCAL; } } @GlideModule public class EncryptedImageGlideModule extends AppGlideModule { @Override public void registerComponents (@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { registry.prepend(EncryptedImageModel.class, InputStream.class, new EncryptedImageModelLoaderFactory ()); } } Glide.with(context) .load(new EncryptedImageModel (encryptedFile, secretKey)) .into(imageView);
public class BlurTransformation extends BitmapTransformation { private static final String ID = "com.example.BlurTransformation" ; private static final int MAX_RADIUS = 25 ; private final int radius; private final int sampling; public BlurTransformation (int radius) { this (radius, 1 ); } public BlurTransformation (int radius, int sampling) { this .radius = Math.min(radius, MAX_RADIUS); this .sampling = sampling; } @Override protected Bitmap transform (@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { int width = toTransform.getWidth(); int height = toTransform.getHeight(); int scaledWidth = width / sampling; int scaledHeight = height / sampling; Bitmap bitmap = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); if (bitmap == null ) { bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); } Canvas canvas = new Canvas (bitmap); canvas.scale(1f / sampling, 1f / sampling); Paint paint = new Paint (Paint.FILTER_BITMAP_FLAG); canvas.drawBitmap(toTransform, 0 , 0 , paint); if (Build.VERSION.SDK_INT >= 31 ) { RenderEffect effect = RenderEffect.createBlurEffect( radius, radius, Shader.TileMode.CLAMP); } else { blur(bitmap, radius); } return bitmap; } private static void blur (Bitmap bitmap, int radius) { int [] pix = new int [bitmap.getWidth() * bitmap.getHeight()]; bitmap.getPixels(pix, 0 , bitmap.getWidth(), 0 , 0 , bitmap.getWidth(), bitmap.getHeight()); int wm = bitmap.getWidth() - 1 ; int hm = bitmap.getHeight() - 1 ; int wh = bitmap.getWidth() * bitmap.getHeight(); int div = radius + radius + 1 ; int [] r = new int [wh]; int [] g = new int [wh]; int [] b = new int [wh]; int [] vmin = new int [Math.max(bitmap.getWidth(), bitmap.getHeight())]; int [] sir = new int [div]; int [] rbs = new int [div]; int [] gbs = new int [div]; int [] bbs = new int [div]; bitmap.setPixels(pix, 0 , bitmap.getWidth(), 0 , 0 , bitmap.getWidth(), bitmap.getHeight()); } @Override public boolean equals (Object o) { if (o instanceof BlurTransformation) { BlurTransformation other = (BlurTransformation) o; return radius == other.radius && sampling == other.sampling; } return false ; } @Override public int hashCode () { return ID.hashCode() + radius * 1000 + sampling * 10 ; } @Override public void updateDiskCacheKey (@NonNull MessageDigest messageDigest) { messageDigest.update((ID + radius + sampling).getBytes(CHARSET)); } }
十一、Glide vs Coil vs Fresco 对比 11.1 架构对比
特性
Glide
Coil
Fresco
开发团队
Google/bumptech
Instacart
Meta(Facebook)
语言
Java (Kotlin extensions)
Kotlin
Java
最小 SDK
API 14
API 14
API 9
APK 体积
~500KB
~200KB
~1200KB
图片格式
PNG/JPEG/GIF/WebP/AVIF
PNG/JPEG/GIF/WebP/AVIF/SVG
PNG/JPEG/GIF/WebP
Animatable
GIF/WebP
GIF/WebP
GIF/WebP
Bitmap Pool
有
有
有
生命周期
Fragment
Lifecycle/LifecycleOwner
无自动(需手动)
磁盘缓存
DiskLruCache
DiskLruCache
DiskLruCache
内存占用
小(复用 Bitmap)
极小(Kotlin 协程)
大(多份 Bitmap 备份)
动图支持
原生
原生
原生 + Drawee 控件
圆角/变换
Transformation 接口
Transformation 接口
Postprocessor
11.2 使用场景推荐
Glide :最通用,Google 推荐,生态最好,国内 App 的首选
Coil :纯 Kotlin 项目,Compose first,轻量级首选
Fresco :需要高级图像处理(渐进式 JPEG、动图内存严格管控),但体积大、侵入性强(需使用 DraweeView)
十二、性能优化要点总结 12.1 常规优化 Glide.with(context) .load(url) .override (300 , 300 ) Glide.with(context) .load(url) .set (DecodeFormat.PREFER_RGB_565) Glide.with(context) .load(nextPageUrl) .preload(300 , 300 ) Glide.with(context) .load(url) .diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.NONE) Glide.with(context) .load(url) .skipMemoryCache(true ) Glide.with(context) .load(highResUrl) .thumbnail(Glide.with(context) .load(lowResUrl) .override (100 , 100 )) .into(imageView)
12.2 RecyclerView 优化 recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged (recyclerView: RecyclerView , newState: Int ) { if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) { Glide.with(this @MyActivity ).pauseRequests() } else { Glide.with(this @MyActivity ).resumeRequests() } } }) override fun onViewRecycled (holder: ViewHolder ) { super .onViewRecycled(holder) Glide.with(holder.imageView).clear(holder.imageView) }
12.3 自定义 GlideModule 全局配置 @GlideModule class MyGlideModule : AppGlideModule () { override fun applyOptions (context: Context , builder: GlideBuilder ) { builder.setMemoryCache(LruResourceCache(64 * 1024 * 1024 )) builder.setDiskCache( InternalCacheDiskCacheFactory(context, "image_cache" , 250 * 1024 * 1024 ) ) builder.setBitmapPool(LruBitmapPool(32 * 1024 * 1024 )) builder.setLogLevel(Log.DEBUG) builder.setDefaultRequestOptions( RequestOptions() .format(DecodeFormat.PREFER_ARGB_8888) .disallowHardwareConfig() ) } override fun registerComponents (context: Context , glide: Glide , registry: Registry ) { } override fun isManifestParsingEnabled () : Boolean { return false } }
十三、总结 Glide 的架构设计体现了大量精妙的工程实践:
Fragment 生命周期注入 :无需用户手动管理,利用 Fragment 的生命周期自动控制请求的暂停/恢复/取消
两层内存缓存 + 引用计数 :ActiveResources 保护正在使用的 Bitmap,LruResourceCache 缓存近期使用的,BitmapPool 复用内存
EngineKey 的细节 :缓存 Key 包含尺寸、配置、变换等所有变量,保证同一 URL 可按不同需求缓存多份
Downsampler 的尺寸适配 :根据 ImageView 实际尺寸计算 inSampleSize,配合 BitmapRegionDecoder 处理超大图
DecodeJob 的阶段管道 :ResourceCache → DataCache → Source → Encode,每个阶段可独立失败和重试
请求去重 :Engine 的 jobs Map 确保同一 Key 只加载一次,结果多路分发
对 Glide 源码的深入理解,是每个 Android 开发者从”会用框架”迈向”能设计框架”的重要一步。
参考资源