Glide(四):强大的图片缓存池和复用机制

Glide有一个很强大的Glide缓存策略和Glide复用机制,之前在里面栽过跟头,借此好好整理总结。

文章目录

    • Glide缓存策略
    • Glide复用机制
      • 1. 缓存和复用机制的区别和作用
      • 2. 原理是什么
        • (1)内存占用
        • (2)inMutable
        • (3)Bitmap复用使用条件
        • (4)inMutable、inBitmap使用
        • (5)Lru算法
      • 3.源码分析
        • DownSampler
        • LruBitmapPool
        • SizeConfigStrategy
        • 什么时候加入复用池?
        • 什么时候从复用池取出?
        • 什么时候从复用池删除?
    • 总结
    • 参考


Glide缓存策略

Glide使用了类似三级缓存策略,分别是弱引用缓存、LruCache缓存、DiskLruCache缓存和网络加载。

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?
前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。


Glide复用机制

除去了Glide的缓存机制以外,还有一个很强大的复用机制

1. 缓存和复用机制的区别和作用

简单来说,缓存是将数据存储起来,下次需要时就不用重新加载数据,直接拿来即用,作用是加快加载速度、避免相同的数据占用空间,降低内存占用;

复用的意思是重新使用,将已经不需要使用的数据空间重新拿来使用,他的作用是避免频繁申请内存,避免OOM,因为在短时间内快速申请释放内存,因为GC不及时,可能在短时间内来不及回收到足够的空间,导致OOM。所以复用的作用是减少内存抖动。

2. 原理是什么

(1)内存占用

在讲解glide如何复用bitmap之前,先来了解bitmap的内存占用问题。

在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中,而像素数据的内存是分配在Native堆中。

而到了Android3.0之后,Bitmap的内存则已经全部分配在VM堆上。意味着我们不需要手动释放bitmap内存,gc内存管理机制会帮忙管理。

(2)inMutable

inMutable是glide能够复用的基石,是bitmapFactory提供的一个参数,表示该bitmap是可变的,支持复用的。

Bitmap是android中最容易造成OOM的元凶之一,在Bitmap的解析参数,BitmapFactory.Options中提供了两个属性:inMutable、inBitmap

详情参考之前的文章《bitmap疑惑》

当你进行Bitmap的复用,需要设置inMutable为true,inBitmap设置想已经存在的Bitmap。

所谓复用的意思,就是将废弃不用准备recyle的bitmap,重新拿过来使用。

在使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存,而使用了inBitmap后, Bitmap的内存是被重新利用的。

(3)Bitmap复用使用条件

在Android 4.4之前,仅支持相同大小的bitmap,inSampleSize必须为1,而且必须采用jpeg或png格式。

在Android 4.4之后只有一个限制,就是被复用的bitmap尺寸要大于 新的bitmap,简单来说就是大图可以给小图复用。

(4)inMutable、inBitmap使用

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

(5)Lru算法

虽然说,将不再使用的bitmap保存起来能够避免频繁申请内存,减少内存抖动,但是同时也会耗用内存存储这些已经不再使用的bitmap,因此Glide使用LRU算法,类似LruCache,有一个最大空间限制。

3.源码分析

真正解析的是DownSampler类

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 负责真正的解析加载图片,过载过程,

  1. inTempStorage

inTempStorage是一个bitmap解析的参数,带入一个buffer,创建临时文件,将图片存储的中间缓存空间

 BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();

 bitmapFactoryOptions.inTempStorage = bytesForOptions;
   
 byteArrayPool.put(bytesForOptions);
  1. getDefaultOptions()

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

LruBitmapPool

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

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格式?

答案就是:

  1. 如果是首次加载,可以直接在Option指定;

  2. 如果是已经加载过的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();
    }
  }

总结

  1. Glide缓存:
    活动资源 (Active Resources)
    内存缓存 (Memory cache)
    资源类型(Resource
    数据来源 (Data)

  2. Glide复用:
    BitmapPool(LruBitmapPool)

参考

https://muyangmin.github.io/glide-docs-cn/doc/caching.html

https://blog.csdn.net/u011035622/article/details/50277565

你可能感兴趣的:(android)