com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glide Show documentation
Show all versions of glide Show documentation
A fast and efficient image loading library for Android focused on smooth scrolling.
package com.bumptech.glide.load.engine.bitmap_recycle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.
private 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.
*/
@VisibleForTesting 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 groupedMap = new GroupedLinkedMap<>();
private final KeyPool keyPool = new KeyPool();
private final Map, NavigableMap> sortedSizes = new HashMap<>();
private final Map, 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;
}
@Deprecated
@Override
public void put(T array, Class arrayClass) {
put(array);
}
@Override
public synchronized void put(T array) {
@SuppressWarnings("unchecked")
Class arrayClass = (Class) array.getClass();
ArrayAdapterInterface 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 sizes = getSizesForAdapter(arrayClass);
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
currentSize += arrayBytes;
evict();
}
@Override
public synchronized T getExact(int size, Class arrayClass) {
Key key = keyPool.get(size, arrayClass);
return getForKey(key, arrayClass);
}
@Override
public synchronized T get(int size, Class arrayClass) {
Integer possibleSize = getSizesForAdapter(arrayClass).ceilingKey(size);
final Key key;
if (mayFillRequest(size, possibleSize)) {
key = keyPool.get(possibleSize, arrayClass);
} else {
key = keyPool.get(size, arrayClass);
}
return getForKey(key, arrayClass);
}
private T getForKey(Key key, Class arrayClass) {
ArrayAdapterInterface arrayAdapter = getAdapterFromType(arrayClass);
T 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 " + key.size + " bytes");
}
result = arrayAdapter.newArray(key.size);
}
return result;
}
// Our cast is safe because the Key is based on the type.
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
@Nullable
private 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
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
evictToSize(maxSize / 2);
}
}
private void evict() {
evictToSize(maxSize);
}
private void evictToSize(int size) {
while (currentSize > size) {
Object evicted = groupedMap.removeLast();
Preconditions.checkNotNull(evicted);
ArrayAdapterInterface