package com.bumptech.glide.load.engine.bitmap_recycle;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
/**
* A fixed size Array Pool that evicts arrays using an LRU strategy to keep the pool under
* the maximum byte size.
*/
public final class LruArrayPool implements ArrayPool {
// 4MB.
static final int DEFAULT_SIZE = 4 * 1024 * 1024;
/**
* The maximum number of times larger an int array may be to be than a requested size to eligible
* to be returned from the pool.
*/
private static final int MAX_OVER_SIZE_MULTIPLE = 8;
/** Used to calculate the maximum % of the total pool size a single byte array may consume. */
private static final int SINGLE_ARRAY_MAX_SIZE_DIVISOR = 2;
private final GroupedLinkedMap<Key, Object> groupedMap = new GroupedLinkedMap<>();
private final KeyPool keyPool = new KeyPool();
private final Map<Class<?>, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
private final Map<Class<?>, ArrayAdapterInterface<?>> adapters = new HashMap<>();
private final int maxSize;
private int currentSize;
@VisibleForTesting
public LruArrayPool() {
maxSize = DEFAULT_SIZE;
}
/**
* Constructor for a new pool.
*
* @param maxSize The maximum size in integers of the pool.
*/
public LruArrayPool(int maxSize) {
this.maxSize = maxSize;
}
@Override
public synchronized <T> void put(T array, Class<T> arrayClass) {
ArrayAdapterInterface<T> arrayAdapter = getAdapterFromType(arrayClass);
int size = arrayAdapter.getArrayLength(array);
int arrayBytes = size * arrayAdapter.getElementSizeInBytes();
if (!isSmallEnoughForReuse(arrayBytes)) {
return;
}
Key key = keyPool.get(size, arrayClass);
groupedMap.put(key, array);
NavigableMap<Integer, Integer> sizes = getSizesForAdapter(arrayClass);
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
currentSize += arrayBytes;
evict();
}
@Override
public <T> T get(int size, Class<T> arrayClass) {
ArrayAdapterInterface<T> arrayAdapter = getAdapterFromType(arrayClass);
T result;
synchronized (this) {
Integer possibleSize = getSizesForAdapter(arrayClass).ceilingKey(size);
final Key key;
if (mayFillRequest(size, possibleSize)) {
key = keyPool.get(possibleSize, arrayClass);
} else {
key = keyPool.get(size, arrayClass);
}
result = getArrayForKey(key);
if (result != null) {
currentSize -= arrayAdapter.getArrayLength(result) * arrayAdapter.getElementSizeInBytes();
decrementArrayOfSize(arrayAdapter.getArrayLength(result), arrayClass);
}
}
if (result == null) {
if (Log.isLoggable(arrayAdapter.getTag(), Log.VERBOSE)) {
Log.v(arrayAdapter.getTag(), "Allocated " + size + " bytes");
}
result = arrayAdapter.newArray(size);
}
return result;
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T getArrayForKey(Key key) {
return (T) groupedMap.get(key);
}
private boolean isSmallEnoughForReuse(int byteSize) {
return byteSize <= maxSize / SINGLE_ARRAY_MAX_SIZE_DIVISOR;
}
private boolean mayFillRequest(int requestedSize, Integer actualSize) {
return actualSize != null
&& (isNoMoreThanHalfFull() || actualSize <= (MAX_OVER_SIZE_MULTIPLE * requestedSize));
}
private boolean isNoMoreThanHalfFull() {
return currentSize == 0 || (maxSize / currentSize >= 2);
}
@Override
public synchronized void clearMemory() {
evictToSize(0);
}
@Override
public synchronized void trimMemory(int level) {
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
evictToSize(maxSize / 2);
}
}
private void evict() {
evictToSize(maxSize);
}
private void evictToSize(int size) {
while (currentSize > size) {
Object evicted = groupedMap.removeLast();
Preconditions.checkNotNull(evicted);
ArrayAdapterInterface<Object> arrayAdapter = getAdapterFromObject(evicted);
currentSize -= arrayAdapter.getArrayLength(evicted) * arrayAdapter.getElementSizeInBytes();
decrementArrayOfSize(arrayAdapter.getArrayLength(evicted), evicted.getClass());
if (Log.isLoggable(arrayAdapter.getTag(), Log.VERBOSE)) {
Log.v(arrayAdapter.getTag(), "evicted: " + arrayAdapter.getArrayLength(evicted));
}
}
}
private void decrementArrayOfSize(int size, Class<?> arrayClass) {
NavigableMap<Integer, Integer> sizes = getSizesForAdapter(arrayClass);
Integer current = sizes.get(size);
if (current == null) {
throw new NullPointerException(
"Tried to decrement empty size" + ", size: " + size + ", this: " + this);
}
if (current == 1) {
sizes.remove(size);
} else {
sizes.put(size, current - 1);
}
}
private NavigableMap<Integer, Integer> getSizesForAdapter(Class<?> arrayClass) {
NavigableMap<Integer, Integer> sizes = sortedSizes.get(arrayClass);
if (sizes == null) {
sizes = new TreeMap<>();
sortedSizes.put(arrayClass, sizes);
}
return sizes;
}
@SuppressWarnings("unchecked")
private <T> ArrayAdapterInterface<T> getAdapterFromObject(T object) {
return (ArrayAdapterInterface<T>) getAdapterFromType(object.getClass());
}
@SuppressWarnings("unchecked")
private <T> ArrayAdapterInterface<T> getAdapterFromType(Class<T> arrayPoolClass) {
ArrayAdapterInterface<?> adapter = adapters.get(arrayPoolClass);
if (adapter == null) {
if (arrayPoolClass.equals(int[].class)) {
adapter = new IntegerArrayAdapter();
} else if (arrayPoolClass.equals(byte[].class)) {
adapter = new ByteArrayAdapter();
} else {
throw new IllegalArgumentException("No array pool found for: "
+ arrayPoolClass.getSimpleName());
}
adapters.put(arrayPoolClass, adapter);
}
return (ArrayAdapterInterface<T>) adapter;
}
// VisibleForTesting
int getCurrentSize() {
int currentSize = 0;
for (Class<?> type : sortedSizes.keySet()) {
for (Integer size : sortedSizes.get(type).keySet()) {
ArrayAdapterInterface<?> adapter = getAdapterFromType(type);
currentSize += size * sortedSizes.get(type).get(size) * adapter.getElementSizeInBytes();
}
}
return currentSize;
}
private static final class KeyPool extends BaseKeyPool<Key> {
@Synthetic
KeyPool() { }
Key get(int size, Class<?> arrayClass) {
Key result = get();
result.init(size, arrayClass);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
private static final class Key implements Poolable {
private final KeyPool pool;
@Synthetic int size;
private Class<?> arrayClass;
Key(KeyPool pool) {
this.pool = pool;
}
void init(int length, Class<?> arrayClass) {
this.size = length;
this.arrayClass = arrayClass;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size && arrayClass == other.arrayClass;
}
return false;
}
@Override
public String toString() {
return "Key{" + "size=" + size + "array=" + arrayClass + '}';
}
@Override
public void offer() {
pool.offer(this);
}
@Override
public int hashCode() {
int result = size;
result = 31 * result + (arrayClass != null ? arrayClass.hashCode() : 0);
return result;
}
}
}