结合 Glide 4.x 的源码,深入讲解 Glide 的资源复用机制,重点分析 BitmapPool
如何实现 Bitmap
复用以减少内存分配。Glide 的 BitmapPool
是其内存优化的核心组件,通过复用 Bitmap
对象,显著降低垃圾回收(GC)压力和内存分配开销,尤其在高频图片加载场景(如列表滑动)下效果明显。以下是 BitmapPool
的实现细节、源码分析、复用流程、优化策略以及关键设计亮点。
1. BitmapPool 的作用与设计目标
BitmapPool
是 Glide 用来管理可复用 Bitmap
对象的内存池,主要目标:
- 减少内存分配 :通过复用已分配的
Bitmap
对象,避免频繁调用Bitmap.createBitmap
。 - 降低 GC 压力 :减少新
Bitmap
对象的创建,降低垃圾回收频率。 - 优化性能 :复用
Bitmap
比新建Bitmap
更快,尤其在内存紧张的设备上。
BitmapPool
的核心思想是维护一个 Bitmap
缓存池,当需要新的 Bitmap
时,优先从池中获取符合条件的 Bitmap
,而不是直接分配新内存。
2. BitmapPool 的核心接口
BitmapPool
是一个接口,定义了管理 Bitmap
的核心方法:
源码分析:BitmapPool
接口
java
public interface BitmapPool {
// 将 Bitmap 放入池中
void put(Bitmap bitmap);
// 获取指定宽高和配置的 Bitmap
@Nullable
Bitmap get(int width, int height, Bitmap.Config config);
// 获取或创建指定宽高和配置的 Bitmap
@NonNull
Bitmap getDirty(int width, int height, Bitmap.Config config);
// 清理池中的所有 Bitmap
void clearMemory();
// 修剪池的内存到指定大小
void trimMemory(int level);
}
- 方法说明 :
put(Bitmap)
:将不再使用的Bitmap
放入池中,供后续复用。get(width, height, config)
:尝试从池中获取匹配尺寸和配置的Bitmap
,返回null
如果没有合适的。getDirty(width, height, config)
:获取一个Bitmap
,如果池中没有则新建一个。clearMemory()
:清空池中的所有Bitmap
,通常在内存压力大时调用。trimMemory(level)
:根据内存级别(如TRIM_MEMORY_MODERATE
)释放部分Bitmap
。
默认实现:
Glide 默认使用 LruBitmapPool
实现 BitmapPool
,基于 LRU(Least Recently Used)算法管理 Bitmap
。
3. LruBitmapPool 的实现细节
LruBitmapPool
是 Glide 的默认 BitmapPool
实现,结合 LRU 算法和尺寸匹配策略管理 Bitmap
。
源码分析:LruBitmapPool
java
public class LruBitmapPool implements BitmapPool {
private final LruPoolStrategy strategy;
private final Set<Bitmap.Config> allowedConfigs;
private final long maxSize;
private long currentSize;
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
return;
}
if (!bitmap.isMutable()) {
bitmap.recycle();
return;
}
if (!allowedConfigs.contains(bitmap.getConfig())) {
bitmap.recycle();
return;
}
final int size = Util.getBitmapByteSize(bitmap);
if (size > maxSize) {
bitmap.recycle();
return;
}
strategy.put(bitmap);
currentSize += size;
evict();
}
@Override
@Nullable
public synchronized Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
if (result != null) {
currentSize -= Util.getBitmapByteSize(result);
}
return result;
}
private void evict() {
while (currentSize > maxSize) {
Bitmap removed = strategy.removeLast();
if (removed == null) {
currentSize = 0;
return;
}
currentSize -= Util.getBitmapByteSize(removed);
removed.recycle();
}
}
}
-
关键字段:
strategy
:LruPoolStrategy
接口的实现,负责Bitmap
的存储和查找逻辑,默认是SizeConfigStrategy
。allowedConfigs
:允许的Bitmap.Config
(如ARGB_8888
、RGB_565
),默认只允许可变(mutable
)的配置。maxSize
:池的最大内存大小,默认基于设备内存计算(通常为内存的 1/8)。currentSize
:当前池中Bitmap
占用的内存大小。
-
核心逻辑:
- put(Bitmap) :
- 检查
Bitmap
是否为null
、是否可变(isMutable
)、是否符合allowedConfigs
、是否超出maxSize
。 - 如果不满足条件,直接调用
bitmap.recycle()
回收。 - 否则,通过
strategy.put(bitmap)
存入池中,更新currentSize
。 - 调用
evict()
确保池大小不超过maxSize
,移除最久未使用的Bitmap
。
- 检查
- get(width, height, config) :
- 通过
strategy.get(width, height, config)
查找匹配的Bitmap
。 - 如果找到,更新
currentSize
并返回。 - 如果未找到,返回
null
。
- 通过
- evict() :
- 当
currentSize
超过maxSize
时,循环调用strategy.removeLast()
移除最旧的Bitmap
,并回收。
- 当
- put(Bitmap) :
LruPoolStrategy
LruPoolStrategy
是一个接口,定义了 Bitmap
的存储、获取和移除逻辑,默认实现是 SizeConfigStrategy
。
源码分析:SizeConfigStrategy
java
class SizeConfigStrategy implements LruPoolStrategy {
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
private final SortedMap<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new EnumMap<>(Bitmap.Config.class);
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig());
groupedMap.put(key, bitmap);
NavigableMap<Integer, Integer> sizes = sortedSizes.get(bitmap.getConfig());
if (sizes == null) {
sizes = new TreeMap<>();
sortedSizes.put(bitmap.getConfig(), sizes);
}
Integer current = sizes.get(size);
sizes.put(size, current == null ? 1 : current + 1);
}
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
Key key = keyPool.get(size, config);
Bitmap result = groupedMap.get(key);
if (result != null) {
decrementSize(result.getConfig(), Util.getBitmapByteSize(result));
return result;
}
NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);
if (sizes != null) {
Map.Entry<Integer, Integer> entry = sizes.ceilingEntry(size);
if (entry != null && entry.getKey() <= size * MAX_SIZE_MULTIPLE) {
key = keyPool.get(entry.getKey(), config);
result = groupedMap.get(key);
if (result != null) {
decrementSize(config, entry.getKey());
}
}
}
return result;
}
@Override
@Nullable
public Bitmap removeLast() {
Bitmap removed = groupedMap.removeLast();
if (removed != null) {
int size = Util.getBitmapByteSize(removed);
decrementSize(removed.getConfig(), size);
}
return removed;
}
}
-
核心数据结构:
groupedMap
:GroupedLinkedMap<Key, Bitmap>
,存储Bitmap
和对应的Key
,基于 LRU 顺序。sortedSizes
:EnumMap<Config, NavigableMap<Integer, Integer>>
,按Config
和大小存储Bitmap
的计数。keyPool
:KeyPool
,缓存Key
对象,减少对象创建。Key
:包含Bitmap
的大小(字节)和Config
,用于查找匹配的Bitmap
。
-
核心逻辑:
- put(Bitmap) :
- 计算
Bitmap
的字节大小(width * height * bytesPerPixel
)。 - 创建
Key
(包含大小和Config
),存入groupedMap
。 - 更新
sortedSizes
,记录该大小和Config
的Bitmap
计数。
- 计算
- get(width, height, config) :
- 计算请求的
Bitmap
大小。 - 优先查找精确匹配的
Key
(大小和Config
相同)。 - 如果未找到,查找稍大的
Bitmap
(大小不超过请求大小的MAX_SIZE_MULTIPLE
倍,默认为 8)。 - 返回找到的
Bitmap
,并更新计数。
- 计算请求的
- removeLast() :
- 从
groupedMap
移除最久未使用的Bitmap
。 - 更新
sortedSizes
的计数。
- 从
- put(Bitmap) :
-
关键优化:
- 尺寸模糊匹配 :允许获取稍大的
Bitmap
,通过ceilingEntry
查找接近但不小于请求大小的Bitmap
,减少浪费。 - Config 隔离 :按
Bitmap.Config
分组存储,避免不同格式的Bitmap
混淆。 - LRU 管理 :
GroupedLinkedMap
确保最久未使用的Bitmap
优先被移除。
- 尺寸模糊匹配 :允许获取稍大的
4. Bitmap 复用流程
以下是 BitmapPool
在图片加载中的完整复用流程:
-
加载请求触发:
Engine.load()
创建DecodeJob
,准备加载图片。DecodeJob
调用Decoder
(如StreamBitmapDecoder
)解码数据。
-
Bitmap 获取:
- 解码器通过
BitmapPool.get(width, height, config)
请求Bitmap
。 - 如果池中有匹配的
Bitmap
,直接返回并重用。 - 如果池中没有,调用
Bitmap.createBitmap(width, height, config)
创建新Bitmap
。
- 解码器通过
-
Bitmap 使用:
- 解码器将数据解码到复用的
Bitmap
上(如通过BitmapFactory.decodeStream
)。 - 解码后的
Bitmap
包装为Resource<Bitmap>
,传递给Target
。
- 解码器将数据解码到复用的
-
Bitmap 释放:
- 当
Resource
不再需要时(Target
调用clear
或资源被缓存替换),调用Resource.recycle()
。 Bitmap
通过BitmapPool.put(bitmap)
放回池中。
- 当
-
内存管理:
- 如果池大小超过
maxSize
,evict()
移除最旧的Bitmap
并调用bitmap.recycle()
。 - 在内存压力下,
clearMemory()
或trimMemory()
清空或修剪池。
- 如果池大小超过
源码分析:StreamBitmapDecoder
java
public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
private final BitmapPool bitmapPool;
private final ArrayPool byteBufferPool;
@Override
public Resource<Bitmap> decode(
@NonNull InputStream source, int width, int height, @NonNull Options options)
throws IOException {
BitmapFactory.Options bitmapOptions = getBitmapOptions();
bitmapOptions.inBitmap = bitmapPool.get(width, height, bitmapOptions.inPreferredConfig);
try {
Bitmap bitmap = BitmapFactory.decodeStream(source, null, bitmapOptions);
return BitmapResource.obtain(bitmap, bitmapPool);
} finally {
if (bitmapOptions.inBitmap != null) {
bitmapPool.put(bitmapOptions.inBitmap);
}
}
}
}
- 作用 :
StreamBitmapDecoder
使用BitmapPool
获取Bitmap
进行解码。 - 关键点 :
bitmapOptions.inBitmap
设置复用的Bitmap
,BitmapFactory
将数据解码到此Bitmap
。- 如果解码失败,
inBitmap
放回池中,避免浪费。 - 解码后的
Bitmap
包装为BitmapResource
,由BitmapPool
管理生命周期。
5. 优化策略与设计亮点
-
尺寸模糊匹配:
-
SizeConfigStrategy
允许获取稍大的Bitmap
,通过MAX_SIZE_MULTIPLE
(默认 8)限制匹配范围,平衡复用率和内存浪费。 -
源码:
javaif (entry.getKey() <= size * MAX_SIZE_MULTIPLE) { // 使用稍大的 Bitmap }
-
-
Config 隔离:
- 按
Bitmap.Config
分组存储(如ARGB_8888
和RGB_565
单独管理),避免格式不匹配。 - 默认只支持可变(
mutable
)的Bitmap
,确保可以复用。
- 按
-
LRU 管理:
- 使用
GroupedLinkedMap
实现 LRU 淘汰,优先移除最久未使用的Bitmap
。 - 通过
sortedSizes
快速查找匹配的Bitmap
。
- 使用
-
内存动态调整:
maxSize
根据设备内存动态计算(参考MemorySizeCalculator
)。- 支持
trimMemory
和clearMemory
,响应系统内存压力。
-
线程安全:
LruBitmapPool
的核心方法使用synchronized
确保线程安全。KeyPool
缓存Key
对象,减少同步操作的开销。
-
异常处理:
- 如果
Bitmap
不可变或格式不支持,直接回收,避免池中存储无效对象。 - 解码失败时,
inBitmap
放回池中,确保资源不浪费。
- 如果
6. 常见问题与解决方案
-
复用率低:
- 问题 :
Bitmap
尺寸或Config
不匹配,导致频繁创建新Bitmap
。 - 解决方案 :
-
统一图片加载的
Config
(如默认ARGB_8888
)。 -
调整
MAX_SIZE_MULTIPLE
,允许更大范围的尺寸匹配。 -
使用
GlideBuilder
增大BitmapPool
大小:javanew GlideBuilder().setBitmapPool(new LruBitmapPool(maxSize * 2));
-
- 问题 :
-
内存泄漏:
- 问题 :
Bitmap
未正确放回池中,导致内存占用增加。 - 解决方案 :
- 确保
Resource.recycle()
被调用(通过RequestManager
绑定生命周期)。 - 检查自定义
Decoder
是否正确处理Bitmap
。
- 确保
- 问题 :
-
解码失败:
- 问题 :复用的
inBitmap
导致BitmapFactory
解码失败(Android 4.4+ 对inBitmap
有严格要求)。 - 解决方案 :
-
使用
Downsampler
的inBitmap
兼容逻辑,确保尺寸和格式匹配。 -
源码:
javaif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { bitmapOptions.inBitmap = bitmapPool.get(width, height, config); }
-
- 问题 :复用的
-
池大小不足:
- 问题 :
maxSize
过小,导致频繁回收Bitmap
。 - 解决方案 :
-
通过
GlideBuilder
增加池大小:javanew GlideBuilder().setBitmapPool(new LruBitmapPool(maxSize * 1.5f));
-
- 问题 :
7. 源码中的典型调用链
以下是 BitmapPool
的典型使用链:
java
// DecodeJob 调用 Decoder
Resource<Bitmap> resource = decoder.decode(source, width, height, options);
// StreamBitmapDecoder 获取 Bitmap
Bitmap bitmap = bitmapPool.get(width, height, config);
bitmapOptions.inBitmap = bitmap;
Bitmap result = BitmapFactory.decodeStream(source, null, bitmapOptions);
// BitmapResource 管理 Bitmap
return BitmapResource.obtain(result, bitmapPool);
// Resource 释放时放回池中
public class BitmapResource implements Resource<Bitmap> {
@Override
public void recycle() {
if (bitmap != null) {
bitmapPool.put(bitmap);
}
}
}
8. 扩展与自定义
开发者可以通过以下方式自定义 BitmapPool
:
-
自定义池大小 :
javalong customMaxSize = Runtime.getRuntime().maxMemory() / 4; new GlideBuilder().setBitmapPool(new LruBitmapPool(customMaxSize));
-
自定义 LruPoolStrategy :
-
实现
LruPoolStrategy
,修改Bitmap
的匹配逻辑(如更宽松的尺寸匹配)。 -
示例:
javaclass CustomStrategy implements LruPoolStrategy { @Override public Bitmap get(int width, int height, Bitmap.Config config) { // 自定义匹配逻辑 } } new GlideBuilder().setBitmapPool(new LruBitmapPool(maxSize, new CustomStrategy()));
-
-
禁用 BitmapPool :
-
使用空实现:
javanew GlideBuilder().setBitmapPool(new BitmapPool() { @Override public void put(Bitmap bitmap) { bitmap.recycle(); } @Override public Bitmap get(int width, int height, Bitmap.Config config) { return null; } // ... });
-
9. 总结
Glide 的 BitmapPool
通过 LruBitmapPool
和 SizeConfigStrategy
实现高效的 Bitmap
复用:
- 核心机制 :维护 LRU 管理的
Bitmap
池,支持尺寸和Config
匹配。 - 流程 :解码时从池中获取
Bitmap
,释放时放回池中,超出大小自动回收。 - 优化:尺寸模糊匹配、Config 隔离、线程安全和动态内存管理。
- 优势:显著减少内存分配和 GC 压力,适合高频加载场景。
- 灵活性 :通过
GlideBuilder
和LruPoolStrategy
支持自定义。