Glide有一个很强大的Glide缓存策略和Glide复用机制,之前在里面栽过跟头,借此好好整理总结。
Glide使用了类似三级缓存策略,分别是弱引用缓存、LruCache缓存、DiskLruCache缓存和网络加载。
默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?
前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。
除去了Glide的缓存机制以外,还有一个很强大的复用机制
简单来说,缓存是将数据存储起来,下次需要时就不用重新加载数据,直接拿来即用,作用是加快加载速度、避免相同的数据占用空间,降低内存占用;
复用的意思是重新使用,将已经不需要使用的数据空间重新拿来使用,他的作用是避免频繁申请内存,避免OOM,因为在短时间内快速申请释放内存,因为GC不及时,可能在短时间内来不及回收到足够的空间,导致OOM。所以复用的作用是减少内存抖动。
在讲解glide如何复用bitmap之前,先来了解bitmap的内存占用问题。
在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中,而像素数据的内存是分配在Native堆中。
而到了Android3.0之后,Bitmap的内存则已经全部分配在VM堆上。意味着我们不需要手动释放bitmap内存,gc内存管理机制会帮忙管理。
inMutable是glide能够复用的基石,是bitmapFactory提供的一个参数,表示该bitmap是可变的,支持复用的。
Bitmap是android中最容易造成OOM的元凶之一,在Bitmap的解析参数,BitmapFactory.Options中提供了两个属性:inMutable、inBitmap
详情参考之前的文章《bitmap疑惑》
当你进行Bitmap的复用,需要设置inMutable为true,inBitmap设置想已经存在的Bitmap。
所谓复用的意思,就是将废弃不用准备recyle的bitmap,重新拿过来使用。
在使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存,而使用了inBitmap后, Bitmap的内存是被重新利用的。
在Android 4.4之前,仅支持相同大小的bitmap,inSampleSize必须为1,而且必须采用jpeg或png格式。
在Android 4.4之后只有一个限制,就是被复用的bitmap尺寸要大于 新的bitmap,简单来说就是大图可以给小图复用。
BitmapFactory.Options largeOption = new BitmapFactory.Options();
largeOption.inMutable = true; // 设置inMutable
Bitmap largeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large, largeOption)
BitmapFactory.Options smallOption = new BitmapFactory.Options();
smallOption.inBitmap = largeBitmap; // 设置inBitmap被复用的Bitmap
Bitmap smallBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.small, smallOption);
虽然说,将不再使用的bitmap保存起来能够避免频繁申请内存,减少内存抖动,但是同时也会耗用内存存储这些已经不再使用的bitmap,因此Glide使用LRU算法,类似LruCache,有一个最大空间限制。
真正解析的是DownSampler类
// DownSampler
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
DownsampleStrategy downsampleStrategy = options.get(DOWNSAMPLE_STRATEGY);
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
if (decodeFormat == DecodeFormat.PREFER_ARGB_8888_DISALLOW_HARDWARE) {
isHardwareConfigAllowed = false;
}
try {
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
DownSampler 负责真正的解析加载图片,过载过程,
inTempStorage是一个bitmap解析的参数,带入一个buffer,创建临时文件,将图片存储的中间缓存空间
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;
byteArrayPool.put(bytesForOptions);
Glide哪里将inMutable设置为true,答应就在getDefaultOptions()中
获取默认的bitmap解析参数,可以看到默认的设置里面inMutable是设为true的。
private static synchronized BitmapFactory.Options getDefaultOptions() {
BitmapFactory.Options decodeBitmapOptions;
synchronized (OPTIONS_QUEUE) {
decodeBitmapOptions = OPTIONS_QUEUE.poll();
}
if (decodeBitmapOptions == null) {
decodeBitmapOptions = new BitmapFactory.Options();
resetOptions(decodeBitmapOptions);
}
return decodeBitmapOptions;
}
private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
decodeBitmapOptions.inTempStorage = null;
decodeBitmapOptions.inDither = false;
decodeBitmapOptions.inScaled = false;
decodeBitmapOptions.inSampleSize = 1;
decodeBitmapOptions.inPreferredConfig = null;
decodeBitmapOptions.inJustDecodeBounds = false;
decodeBitmapOptions.inDensity = 0;
decodeBitmapOptions.inTargetDensity = 0;
decodeBitmapOptions.outWidth = 0;
decodeBitmapOptions.outHeight = 0;
decodeBitmapOptions.outMimeType = null;
decodeBitmapOptions.inBitmap = null;
decodeBitmapOptions.inMutable = true;
}
Glide复用bitmap,总需要一个地方来缓存废弃不用的bitmap,而LruBitmapPool 就是Glide提供Bitmap缓存复用的池。
下面的代码逻辑比较简单,内部其实也是LruCache的思想,真正的实现类是LruPoolStrategy
public class LruBitmapPool implements BitmapPool {
private final LruPoolStrategy strategy;
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;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
dump();
evict();
}
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 = createBitmap(width, height, config);
}
return result;
}
}
SizeConfigStrategy 内部代码逻辑也比较简单,是执行真正实现存取bitmap的策略类。
public class SizeConfigStrategy implements LruPoolStrategy {
private static final int MAX_SIZE_MULTIPLE = 8;
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<>();
// 存入
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 = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
// 取出
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
// 查找出最合适的bitmap
Key bestKey = findBestKey(size, config);
// 取出
Bitmap result = groupedMap.get(bestKey);
if (result != null) {
// Decrement must be called before reconfigure.
decrementBitmapOfSize(bestKey.size, result);
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
}
return result;
}
}
findBestKey()方法就是查找出最合适的key,通过大小匹配
// SizeCofingStrategy
private Key findBestKey(int size, Bitmap.Config config) {
Key result = keyPool.get(size, config);
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result);
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
在这里有个疑问:
从缓存池里获取一张Bitmap了,但是我们如何修改他的Config设置,例如颜色格式、或者更改默认的Config.ARGB_8888格式为ARGB_444格式?
答案就是:
如果是首次加载,可以直接在Option指定;
如果是已经加载过的bitmap,bitmap提供了一个reconfigure()方法进行设置。
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("width and height must be > 0");
}
if (!isMutable()) {
throw new IllegalStateException("only mutable bitmaps may be reconfigured");
}
nativeReconfigure(mNativePtr, width, height, config.nativeInt, mRequestPremultiplied);
mWidth = width;
mHeight = height;
mColorSpace = null;
}
BitmapPool提供了缓存bitmap的地方,那什么时候将bitmap加入BitmapPool呢?
事实上是当各个外部资源recycle的时候,就会加入对应的BitmapPool,而BitmapPool是可配置的,这里只是分析LruBitmapPool
// BitmapDrawableResource
public void recycle() {
bitmapPool.put(drawable.getBitmap());
}
同理,那什么时候从BitmapPool中取出来使用的,来看一个例子就是在DownSamper的decodeFromWrappedStreams,内部有一个取出逻辑
// DownSampler
private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
int requestedHeight, boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks) throws IOException {
//计算原始大小
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
String sourceMimeType = options.outMimeType;
// 计算方向
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
// 计算目标大小
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
// 计算类型
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
...
// 使用复用池
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
...
// If this isn't an image, or BitmapFactory was unable to parse the size, width and height
// will be -1 here.
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);
...
return rotated;
}
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
...
// BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
getDirty()方法,表示在图片加载中,如果可以使用bitmap池,就会调用bitmapPool的getDirty(),最后赋值给inBitmap
// LruBitmapPool
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
// 优先获取
Bitmap result = getDirtyOrNull(width, height, config);
if (result == null) { // 若没有,新建一个bitmap
result = createBitmap(width, height, config);
}
return result;
}
LruBitmapPool内部利用了Lru算法,每次操作都自动检测是否删除多余的缓存
//LruBitmapPool
private void evict() {
trimToSize(maxSize);
}
private synchronized void trimToSize(long size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}
Glide缓存:
活动资源 (Active Resources)
内存缓存 (Memory cache)
资源类型(Resource
数据来源 (Data)
Glide复用:
BitmapPool(LruBitmapPool)
https://muyangmin.github.io/glide-docs-cn/doc/caching.html
https://blog.csdn.net/u011035622/article/details/50277565