Glide的Bitmap的回收机制

Glide提供了Bitmap对象的回收和再利用的机制,可以有效的提高资源的再利用。

Key池

Bitmap是存在map中的,那么存入和读取就需要个key。Glide为我们提供了一个比较好的key的解决方案。

接口定义

BaseKeyPool.java这个是Key池的定义,整个Key是维护在一个Queue中的。

abstract class BaseKeyPool<T extends Poolable> {
  private static final int MAX_SIZE = 20;
  private final Queue<T> keyPool = Util.createQueue(MAX_SIZE);

  T get() {
    T result = keyPool.poll();
    if (result == null) {
      result = create();
    }
    return result;
  }

  public void offer(T key) {
    if (keyPool.size() < MAX_SIZE) {
      keyPool.offer(key);
    }
  }

  abstract T create();
}

Poolable.java是Key定义了key的唯一操作,即添加到队列中

interface Poolable {
  void offer();
}

架构的图:

具体的一个实例

Key:

static final class Key implements Poolable {
    private final KeyPool pool;
    @Synthetic int size;

    Key(KeyPool pool) {
      this.pool = pool;
    }

    public void init(int size) {
      this.size = size;
    }

    @Override
    public boolean equals(Object o) {
      if (o instanceof Key) {
        Key other = (Key) o;
        return size == other.size;
      }
      return false;
    }

    @Override
    public int hashCode() {
      return size;
    }

    @Override
    public String toString() {
      return getBitmapString(size);
    }

    @Override
    public void offer() {
      pool.offer(this);
    }
  }

KeyPool:

static class KeyPool extends BaseKeyPool<Key> {

    public Key get(int size) {
      Key result = super.get();
      result.init(size);
      return result;
    }

    @Override
    protected Key create() {
      return new Key(this);
    }
  }

设计上的亮点

这个设计上的亮点是,我并不实际的存储某某Key的某某bitmap对象,而是只是维护着一个key的队列,从尾部插入,需要就从头部取出来。

那不是取出的key不是我存入的key吗?确实不是的,但没关系,从上面的代码可以看出,取出后,根据我传入的参数重新刷新的Key的值,变成我需要的对象。

也就是说,减去了对象的实例化,共用了对象本身。

Pool池的缓存策略

缓存池提供了3中不同存储策略的池子:

SizeStrategy

根据图片的size不同而进行的缓存策略。因此这个缓存策略key的生成是根据size来的。

主要代码:

@Override
  public void put(Bitmap bitmap) {
    int size = Util.getBitmapByteSize(bitmap);
    final Key key = keyPool.get(size);

    groupedMap.put(key, bitmap);

    Integer current = sortedSizes.get(key.size);
    sortedSizes.put(key.size, current == null ? 1 : current + 1);
  }

  @Override
  @Nullable
  public Bitmap get(int width, int height, Bitmap.Config config) {
    final int size = Util.getBitmapByteSize(width, height, config);
    Key key = keyPool.get(size);

    Integer possibleSize = sortedSizes.ceilingKey(size);
    if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
      keyPool.offer(key);
      key = keyPool.get(possibleSize);
    }

    final Bitmap result = groupedMap.get(key);
    if (result != null) {
      result.reconfigure(width, height, config);
      decrementBitmapOfSize(possibleSize);
    }

    return result;
  }

这里我们主要关注下bitmap的大小的计算:

public static int getBitmapByteSize(int width, int height, @Nullable Bitmap.Config config) {
    return width * height * getBytesPerPixel(config);
  }

  private static int getBytesPerPixel(@Nullable Bitmap.Config config) {
    // A bitmap by decoding a GIF has null "config" in certain environments.
    if (config == null) {
      config = Bitmap.Config.ARGB_8888;
    }

    int bytesPerPixel;
    switch (config) {
      case ALPHA_8:
        bytesPerPixel = 1;
        break;
      case RGB_565:
      case ARGB_4444:
        bytesPerPixel = 2;
        break;
      case RGBA_F16:
        bytesPerPixel = 8;
        break;
      case ARGB_8888:
      default:
        bytesPerPixel = 4;
        break;
    }
    return bytesPerPixel;
  }

AttributeStrategy

根据图片的width、height和config不同而进行的缓存策略。因此这个缓存策略key的生成是根据上面的三个参数来共同决定。

它的主要操作的代码:

@Override
  public void put(Bitmap bitmap) {
    final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

    groupedMap.put(key, bitmap);
  }

  @Override
  public Bitmap get(int width, int height, Bitmap.Config config) {
    final Key key = keyPool.get(width, height, config);

    return groupedMap.get(key);
  }

SizeConfigStrategy

根据图片的size和config不同而进行的缓存策略。因此这个缓存策略key的生成是根据上面的两个参数来共同决定。

Glide是怎么抉择使用的?

private static LruPoolStrategy getDefaultStrategy() {
    final LruPoolStrategy strategy;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      strategy = new SizeConfigStrategy();
    } else {
      strategy = new AttributeStrategy();
    }
    return strategy;
  }

当然我们是可以自己设定这个策略的。

Pool池

Glide为我们提供了2种池子。

  • BitmapPool
  • ArrayPool

BitmapPool

接口定义:

public interface BitmapPool {
  long getMaxSize();
  void setSizeMultiplier(float sizeMultiplier);
  void put(Bitmap bitmap);
  Bitmap get(int width, int height, Bitmap.Config config);
  Bitmap getDirty(int width, int height, Bitmap.Config config);
  void clearMemory();
  void trimMemory(int level);
}

它的实现者是LruBitmapPool.java,具体分析:

Pool缓存策略的选择

如上面

缺省的允许缓存的Bitmap.Config

private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
    Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      // GIFs, among other types, end up with a native Bitmap config that doesn't map to a java
      // config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured
      // and are suitable for re-use.
      configs.add(null);
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      configs.remove(Bitmap.Config.HARDWARE);
    }
    return Collections.unmodifiableSet(configs);
  }

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())) {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Reject bitmap from pool"
                + ", bitmap: " + strategy.logBitmap(bitmap)
                + ", is mutable: " + bitmap.isMutable()
                + ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));
      }
      bitmap.recycle();
      return;
    }

    final int size = strategy.getSize(bitmap);
    strategy.put(bitmap);
    tracker.add(bitmap);

    puts++;
    currentSize += size;

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
    }
    dump();

    evict();
  }

加入的操作是线程安全的。请注意上面的if判断。

ArrayPool

接口的定义:

public interface ArrayPool {
  int STANDARD_BUFFER_SIZE_BYTES = 64 * 1024;
  <T> void put(T array, Class<T> arrayClass);
  <T> void put(T array);
  <T> T get(int size, Class<T> arrayClass);
  <T> T getExact(int size, Class<T> arrayClass);
  void clearMemory();
  void trimMemory(int level);
}

给个结构图,具体代码大同小异:

总结

分析了Glide,那么个人觉得设计一个对象的回收机制主要有下面几点:

  • Key的设计
  • 缓存的策略
  • 具体的缓存池子

results matching ""

    No results matching ""