目录
  1. 1. 一、Glide 概述与整体架构
    1. 1.1. 1.1 Glide 是什么
    2. 1.2. 1.2 整体架构
    3. 1.3. 1.3 一张图的完整旅程
  2. 2. 二、生命周期管理 —— Glide 的独门绝技
    1. 2.1. 2.1 RequestManagerFragment
    2. 2.2. 2.2 Glide.with() 的查找逻辑
    3. 2.3. 2.3 ActivityFragmentLifecycle
    4. 2.4. 2.4 RequestTracker —— 请求生命周期管理
  3. 3. 三、内存缓存设计
    1. 3.1. 3.1 两级内存缓存架构
    2. 3.2. 3.2 ActiveResources
    3. 3.3. 3.3 EngineResource 引用计数
    4. 3.4. 3.4 LruResourceCache —— LRU 驱逐策略
    5. 3.5. 3.5 Engine 中的缓存查询流程
  4. 4. 四、磁盘缓存设计
    1. 4.1. 4.1 DiskLruCacheWrapper
    2. 4.2. 4.2 SafeKeyGenerator —— 缓存 Key 的安全化
    3. 4.3. 4.3 两种磁盘缓存策略
  5. 5. 五、Bitmap 池复用机制
    1. 5.1. 5.1 LruBitmapPool
    2. 5.2. 5.2 LruPoolStrategy —— 两种匹配策略
    3. 5.3. 5.3 什么情况下不进入池
    4. 5.4. 5.4 Bitmap.reconfigure —— 尺寸匹配
  6. 6. 六、下采样(Downsampling)
    1. 6.1. 6.1 Downsampler —— 核心解码器
    2. 6.2. 6.2 计算 inSampleSize
    3. 6.3. 6.3 BitmapRegionDecoder —— 区域解码(超大图)
    4. 6.4. 6.4 硬件 Bitmap(Android O+)
  7. 7. 七、变换管线(Transformation Pipeline)
    1. 7.1. 7.1 Transformation 体系
    2. 7.2. 7.2 BitmapTransformation 基类
    3. 7.3. 7.3 CenterCrop 实现
    4. 7.4. 7.4 MultiTransformation —— 变换组合
  8. 8. 八、GIF 解码
    1. 8.1. 8.1 GifDecoder 架构
    2. 8.2. 8.2 GIF 在 RecyclerView 中的处理
    3. 8.3. 8.3 GIF 帧的内存管理
  9. 9. 九、请求协调
    1. 9.1. 9.1 缩略图(Thumbnail)
    2. 9.2. 9.2 占位图与错误图
    3. 9.3. 9.3 请求去重
  10. 10. 十、自定义 ModelLoader
    1. 10.1. 10.1 从加密文件加载图片
    2. 10.2. 10.2 自定义 Transformation 示例 —— 模糊变换
  11. 11. 十一、Glide vs Coil vs Fresco 对比
    1. 11.1. 11.1 架构对比
    2. 11.2. 11.2 使用场景推荐
  12. 12. 十二、性能优化要点总结
    1. 12.1. 12.1 常规优化
    2. 12.2. 12.2 RecyclerView 优化
    3. 12.3. 12.3 自定义 GlideModule 全局配置
  13. 13. 十三、总结
  14. 14. 参考资源
【必知必会SDK】之Glide

一、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.javaRequestManagerRetriever.java

// Glide.java
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}

// RequestManagerRetriever.java
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 {
// 核心数据结构:弱引用 Map
final Map<Key, ResourceWeakReference> activeEngineResources =
new ArrayMap<>();

// 引用队列:GC 回收后自动加入此队列
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue =
new ReferenceQueue<>();

// 清理线程:Handler + Looper
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();
}
}

// 返回被 GC 回收的资源引用
Reference<EngineResource<?>> poll() {
return resourceReferenceQueue.poll();
}

// ResourceWeakReference 内部类
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;
}
}
}

工作机制

  1. 图片解码完成 → acquire() 使引用计数 +1 → 加入 ActiveResources
  2. 图片显示到 ImageView → acquire() 再次 +1
  3. 当图片不再显示(View 回收、页面切换)→ release() 使引用计数 -1
  4. 引用计数降到 0 → 从 ActiveResources 移除 → 移入 LruResourceCache
  5. 如果 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;

// 获取时+1
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");
}
}

// 释放时-1
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);
}
// 被 LRU 驱逐时,回收 Bitmap
oldValue.recycle();
}

@Override
protected int getSize(EngineResource<?> item) {
return item.getSize();
}
}

继承自 Android SDK 的 android.util.LruCache

// android.util.LruCache 的核心驱逐逻辑(AOSP)
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); // 回调给 Glide
}
}

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) {

// 1. 生成缓存 Key
EngineKey key = keyFactory.buildKey(
model, signature, width, height, transformations,
resourceClass, transcodeClass, options);

// 2. 查 ActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}

// 3. 查 LruResourceCache
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}

// 4. 检查是否有正在加载同一 Key 的 Job(去重)
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}

// 5. 都没有 → 创建 DecodeJob 加载
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; // URL
private final int width; // 目标宽
private final int height; // 目标高
private final Class<?> resourceClass; // Bitmap.class / Drawable.class
private final Class<?> transcodeClass; // BitmapDrawable.class
private final Key signature; // 额外签名(如图片修改时间)
private final Map<Class<?>, Transformation<?>> transformations;
private final Options options; // 解码选项(如 Bitmap.Config)
private final Key originalKey; // 未应用变换前的原始 Key
}

不同的 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; // 智能模式:根据 DataFetcher 的 getDataSource() 判断
}

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; // SizeConfigStrategy 或 AttributeStrategy
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;

// Android O+ 才支持 Bitmap.Config.HARDWARE
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 {
// ARGB_8888: 每像素 4 字节
private static final int BYTES_PER_PIXEL_ARGB_8888 = 4;
// RGB_565: 每像素 2 字节
private static final int BYTES_PER_PIXEL_RGB_565 = 2;

// Key = size(长宽) + config
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; // Bitmap 总字节数
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
}
// 从 GroupedLinkedMap 中取出
Bitmap removed = groupedMap.removeLast(bestKey);
if (removed != null) {
currentSize -= Util.getBitmapByteSize(removed);
// 如果尺寸完全匹配,直接复用;否则调用 reconfigure
if (removed.getWidth() == width && removed.getHeight() == height
&& removed.getConfig() == config) {
// 完美匹配
} else {
removed.reconfigure(width, height, config);
}
}
return removed;
}

5.3 什么情况下不进入池

// LruBitmapPool.put()
@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;

// LRU 驱逐
trimToSize(maxSize);
}

不会入池的情况

  1. Bitmap 不可变(isMutable() == false
  2. Bitmap 尺寸超过池的最大容量(如超大图 > 屏幕尺寸 * 4)
  3. Bitmap.Config 不在允许列表中(如 HARDWARE config 在某些设备上不兼容)
  4. Bitmap 已经被回收(isRecycled() == true

5.4 Bitmap.reconfigure —— 尺寸匹配

Android API 19+ 引入的 Bitmap.reconfigure() 允许修改已有 Bitmap 的尺寸和配置,而不需要重新分配内存:

// android.graphics.Bitmap (AOSP)
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(...);
}
// native 层设置新的宽高和配置
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();

// 最大纹理尺寸(GPU 渲染上限)
private static final int MARK_POSITION = 20 * 1024 * 1024; // 20MB
private static final int MAX_DIMENSION = 16384; // 宽或高的最大值

public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
// 1. 根据 InputStream 类型选择解码路径
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

// Downsampler.calculateScaling()
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)
);
}
// Scaling.FIT: 保证至少一方 >= 目标尺寸
return Math.min(
integerDivision(inWidth, outWidth),
integerDivision(inHeight, outHeight)
);
}

private static int integerDivision(int dividend, int divisor) {
int result = dividend / divisor;
// 如果除不尽,+1 保证 at least
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 进行区域解码

// Downsampler 中的区域解码路径
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 默认禁用 HARDWARE,因为:
// 1. 无法在 Java 层操作像素(getPixels/setPixels 抛异常)
// 2. 无法用于 Bitmap 变换(许多 Glide Transformation 需要像素操作)
// 3. 需要在特定线程读取(GL 线程)
// 4. 不可变(无法入池复用)

// 如果需要启用 HARDWARE:
Glide.with(context)
.load(url)
.set(DecodeFormat.PREFER_ARGB_8888) // 显式禁用 HARDWARE
.into(imageView);

七、变换管线(Transformation Pipeline)

7.1 Transformation 体系

源码位置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:模糊

7.2 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) {
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);

// 如果变换产生了新的 Bitmap,回收旧的
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;
}
}

// TransformationUtils.centerCrop()
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;
}

7.4 MultiTransformation —— 变换组合

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;
}
}

// 使用:先 CenterCrop 再圆角
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 {
// GIF 解析状态
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;

// GIF 扩展块标识
static final int EXTENSION_BLOCK_LABEL = 0x21;
static final int GRAPHIC_CONTROL_EXTENSION = 0xF9; // 帧延迟、透明色
static final int APPLICATION_EXTENSION = 0xFF; // Netscape 循环次数
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; // GIF 文件头(宽、高、颜色表等)
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 消耗:

// GifDrawable.java
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) {
// 前一个 currentFrame → 回收或入池
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) // 先加载原图 10% 尺寸的缩略图
.into(imageView);

// 方式二:指定不同的 URL
Glide.with(context)
.load(highResUrl)
.thumbnail(Glide.with(context).load(lowResUrl)) // 另一个请求
.into(imageView);

源码机制

// RequestBuilder.java
public RequestBuilder<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> thumb) {
this.thumbBuilder = thumb;
return this;
}

// SingleRequest.java
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) // model 为 null 时显示
.into(imageView);

占位图的状态变更SingleRequest.onResourceReady()):

// SingleRequest
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(); // 清除 placeholder
}
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 示例,用于从加密文件加载图片:

// 定义自定义 Model
@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; }
}

// 自定义 ModelLoaderFactory
public class EncryptedImageModelLoaderFactory
implements ModelLoaderFactory<EncryptedImageModel, InputStream> {

@Override
public ModelLoader<EncryptedImageModel, InputStream> build(
@NonNull MultiModelLoaderFactory multiFactory) {
return new EncryptedImageModelLoader();
}

@Override
public void teardown() {}
}

// 自定义 ModelLoader
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;
}
}

// 自定义 DataFetcher
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 {
// AES-GCM 解密
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 注册
@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);

10.2 自定义 Transformation 示例 —— 模糊变换

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 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);

// RenderScript 或自定义模糊算法
// Android 12+ 建议使用 RenderEffect
if (Build.VERSION.SDK_INT >= 31) {
RenderEffect effect = RenderEffect.createBlurEffect(
radius, radius, Shader.TileMode.CLAMP);
// 使用 HardwareRenderer...
} else {
// 使用 RenderScript 或 StackBlur
blur(bitmap, radius);
}

return bitmap;
}

// StackBlur 算法(Java 实现,兼容所有版本)
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];

// ... 完整的 StackBlur 算法(此处省略)
// 参考: http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html

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 常规优化

// 1. 显式指定尺寸(避免等待 View 测量)
Glide.with(context)
.load(url)
.override(300, 300) // 必写:指定加载尺寸

// 2. 选择合适的 Bitmap.Config
Glide.with(context)
.load(url)
.set(DecodeFormat.PREFER_RGB_565) // 无透明度的图用 RGB_565,节省 50% 内存

// 3. 预加载
Glide.with(context)
.load(nextPageUrl)
.preload(300, 300) // 后台提前加载下一页的图

// 4. 磁盘缓存策略
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL) // 常用图缓存原数据+变换后
.diskCacheStrategy(DiskCacheStrategy.NONE) // 敏感图片不落盘

// 5. 跳过内存缓存(大图/一次性图)
Glide.with(context)
.load(url)
.skipMemoryCache(true)

// 6. 缩略图 + 高清图
Glide.with(context)
.load(highResUrl)
.thumbnail(Glide.with(context)
.load(lowResUrl)
.override(100, 100))
.into(imageView)

12.2 RecyclerView 优化

// 1. 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()
}
}
})

// 2. 在 onViewRecycled 中取消加载
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)) // 64MB

// 磁盘缓存大小和位置
builder.setDiskCache(
InternalCacheDiskCacheFactory(context, "image_cache", 250 * 1024 * 1024) // 250MB
)

// Bitmap 池大小
builder.setBitmapPool(LruBitmapPool(32 * 1024 * 1024)) // 32MB

// 自定义日志级别
builder.setLogLevel(Log.DEBUG)

// 默认请求选项
builder.setDefaultRequestOptions(
RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.disallowHardwareConfig() // 允许 Transformation
)
}

override fun registerComponents(context: Context,
glide: Glide, registry: Registry) {
// 注册自定义 ModelLoader、Decoder 等
}

override fun isManifestParsingEnabled(): Boolean {
return false // 禁用 AndroidManifest 扫描,提升启动速度
}
}

十三、总结

Glide 的架构设计体现了大量精妙的工程实践:

  1. Fragment 生命周期注入:无需用户手动管理,利用 Fragment 的生命周期自动控制请求的暂停/恢复/取消
  2. 两层内存缓存 + 引用计数:ActiveResources 保护正在使用的 Bitmap,LruResourceCache 缓存近期使用的,BitmapPool 复用内存
  3. EngineKey 的细节:缓存 Key 包含尺寸、配置、变换等所有变量,保证同一 URL 可按不同需求缓存多份
  4. Downsampler 的尺寸适配:根据 ImageView 实际尺寸计算 inSampleSize,配合 BitmapRegionDecoder 处理超大图
  5. DecodeJob 的阶段管道:ResourceCache → DataCache → Source → Encode,每个阶段可独立失败和重试
  6. 请求去重:Engine 的 jobs Map 确保同一 Key 只加载一次,结果多路分发

对 Glide 源码的深入理解,是每个 Android 开发者从”会用框架”迈向”能设计框架”的重要一步。


参考资源

打赏
  • 微信
  • 支付宝

评论