/** * An interface for adding and removing resources from an in memory cache. */ publicinterfaceMemoryCache{ /** * An interface that will be called whenever a bitmap is removed from the cache. */ interfaceResourceRemovedListener{ voidonResourceRemoved(@NonNull Resource<?> removed); }
/** * Returns the sum of the sizes of all the contents of the cache in bytes. */ longgetCurrentSize();
/** * Returns the current maximum size in bytes of the cache. */ longgetMaxSize();
/** * Adjust the maximum size of the cache by multiplying the original size of the cache by the given * multiplier. * * <p> If the size multiplier causes the size of the cache to be decreased, items will be evicted * until the cache is smaller than the new size. </p> * * @param multiplier A size multiplier >= 0. */ voidsetSizeMultiplier(float multiplier);
/** * Removes the value for the given key and returns it if present or null otherwise. * * @param key The key. */ @Nullable Resource<?> remove(@NonNull Key key);
/** * Add bitmap to the cache with the given key. * * @param key The key to retrieve the bitmap. * @param resource The {@link com.bumptech.glide.load.engine.EngineResource} to store. * @return The old value of key (null if key is not in map). */ @Nullable Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);
/** * Set the listener to be called when a bitmap is removed from the cache. * * @param listener The listener. */ voidsetResourceRemovedListener(@NonNull ResourceRemovedListener listener);
/** * Evict all items from the memory cache. */ voidclearMemory();
/** * Trim the memory cache to the appropriate level. Typically called on the callback onTrimMemory. * * @param level This integer represents a trim level as specified in {@link * android.content.ComponentCallbacks2}. */ voidtrimMemory(int level); }
/** * A resource interface that wraps a particular type so that it can be pooled and reused. * * @param <Z> The type of resource wrapped by this class. */ publicinterfaceResource<Z> {
/** * Returns the {@link Class} of the wrapped resource. */ @NonNull Class<Z> getResourceClass();
/** * Returns an instance of the wrapped resource. * * <p> Note - This does not have to be the same instance of the wrapped resource class and in fact * it is often appropriate to return a new instance for each call. For example, * {@link android.graphics.drawable.Drawable Drawable}s should only be used by a single * {@link android.view.View View} at a time so each call to this method for Resources that wrap * {@link android.graphics.drawable.Drawable Drawable}s should always return a new * {@link android.graphics.drawable.Drawable Drawable}. </p> */ @NonNull Z get();
/** * Returns the size in bytes of the wrapped resource to use to determine how much of the memory * cache this resource uses. */ intgetSize();
/** * Cleans up and recycles internal resources. * * <p> It is only safe to call this method if there are no current resource consumers and if this * method has not yet been called. Typically this occurs at one of two times: * <ul> * <li>During a resource load when the resource is transformed or transcoded before any consumer * have ever had access to this resource</li> * <li>After all consumers have released this resource and it has been evicted from the cache * </li> * </ul> * * For most users of this class, the only time this method should ever be called is during * transformations or transcoders, the framework will call this method when all consumers have * released this resource and it has been evicted from the cache. </p> */ voidrecycle(); }
/** * A general purpose size limited cache that evicts items using an LRU algorithm. By default every * item is assumed to have a size of one. Subclasses can override {@link #getSize(Object)}} to * change the size on a per item basis. * * @param <T> The type of the keys. * @param <Y> The type of the values. */ publicclassLruCache<T, Y> { // LinkedHashMap 的最后一个参数是 accessOrder,设置为 true 后,每次我们访问一个 // 元素,对应的元素都会被移到链表的末尾 privatefinal Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true); privatefinallong initialMaxSize; privatelong maxSize; privatelong currentSize;
/** * Constructor for LruCache. * * @param size The maximum size of the cache, the units must match the units used in {@link * #getSize(Object)}. */ publicLruCache(long size){ this.initialMaxSize = size; this.maxSize = size; }
/** * Sets a size multiplier that will be applied to the size provided in the constructor to put the * new size of the cache. If the new size is less than the current size, entries will be evicted * until the current size is less than or equal to the new size. * * @param multiplier The multiplier to apply. */ publicsynchronizedvoidsetSizeMultiplier(float multiplier){ if (multiplier < 0) { thrownew IllegalArgumentException("Multiplier must be >= 0"); } maxSize = Math.round(initialMaxSize * multiplier); evict(); }
/** * Returns the size of a given item, defaulting to one. The units must match those used in the * size passed in to the constructor. Subclasses can override this method to return sizes in * various units, usually bytes. * * @param item The item to get the size of. */ protectedintgetSize(@Nullable Y item){ return1; }
/** * Returns the number of entries stored in cache. */ protectedsynchronizedintgetCount(){ return cache.size(); }
/** * A callback called whenever an item is evicted from the cache. Subclasses can override. * * @param key The key of the evicted item. * @param item The evicted item. */ protectedvoidonItemEvicted(@NonNull T key, @Nullable Y item){ // optional override }
/** * Returns the current maximum size of the cache in bytes. */ publicsynchronizedlonggetMaxSize(){ return maxSize; }
/** * Returns the sum of the sizes of all items in the cache. */ publicsynchronizedlonggetCurrentSize(){ return currentSize; }
/** * Returns true if there is a value for the given key in the cache. * * @param key The key to check. */
publicsynchronizedbooleancontains(@NonNull T key){ return cache.containsKey(key); }
/** * Returns the item in the cache for the given key or null if no such item exists. * * @param key The key to check. */ @Nullable publicsynchronized Y get(@NonNull T key){ return cache.get(key); }
/** * Adds the given item to the cache with the given key and returns any previous entry for the * given key that may have already been in the cache. * * <p>If the size of the item is larger than the total cache size, the item will not be added to * the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with * the given key and item. * * @param key The key to add the item at. * @param item The item to add. */ @Nullable publicsynchronized Y put(@NonNull T key, @Nullable Y item){ finalint itemSize = getSize(item); if (itemSize >= maxSize) { onItemEvicted(key, item); returnnull; }
if (item != null) { currentSize += itemSize; } @Nullablefinal Y old = cache.put(key, item); if (old != null) { currentSize -= getSize(old);
if (!old.equals(item)) { onItemEvicted(key, old); } } evict();
return old; }
/** * Removes the item at the given key and returns the removed item if present, and null otherwise. * * @param key The key to remove the item at. */ @Nullable publicsynchronized Y remove(@NonNull T key){ final Y value = cache.remove(key); if (value != null) { currentSize -= getSize(value); } return value; }
/** * Clears all items in the cache. */ publicvoidclearMemory(){ trimToSize(0); }
/** * Removes the least recently used items from the cache until the current size is less than the * given size. * * @param size The size the cache should be less than. */ protectedsynchronizedvoidtrimToSize(long size){ Map.Entry<T, Y> last; Iterator<Map.Entry<T, Y>> cacheIterator; while (currentSize > size) { cacheIterator = cache.entrySet().iterator(); last = cacheIterator.next(); final Y toRemove = last.getValue(); currentSize -= getSize(toRemove); final T key = last.getKey(); cacheIterator.remove(); onItemEvicted(key, toRemove); } }
/** * An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */ publicclassLruResourceCacheextendsLruCache<Key, Resource<?>> implementsMemoryCache{ private ResourceRemovedListener listener;
/** * Constructor for LruResourceCache. * * @param size The maximum size in bytes the in memory cache can use. */ publicLruResourceCache(long size){ super(size); }
@SuppressLint("InlinedApi") @Override publicvoidtrimMemory(int level){ if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Entering list of cached background apps // Evict our entire bitmap cache clearMemory(); } elseif (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { // The app's UI is no longer visible, or app is in the foreground but system is running // critically low on memory // Evict oldest half of our bitmap cache trimToSize(getMaxSize() / 2); } } }
/** * Interface for an array pool that pools arrays of different types. */ publicinterfaceArrayPool{ /** * A standard size to use to increase hit rates when the required size isn't defined. * Currently 64KB. */ int STANDARD_BUFFER_SIZE_BYTES = 64 * 1024;
/** * Optionally adds the given array of the given type to the pool. * * <p>Arrays may be ignored, for example if the array is larger than the maximum size of the * pool. * * @deprecated Use {@link #put(Object)} */ @Deprecated <T> voidput(T array, Class<T> arrayClass);
/** * Optionally adds the given array of the given type to the pool. * * <p>Arrays may be ignored, for example if the array is larger than the maximum size of the * pool. */ <T> voidput(T array);
/** * Returns a non-null array of the given type with a length >= to the given size. * * <p>If an array of the given size isn't in the pool, a new one will be allocated. * * <p>This class makes no guarantees about the contents of the returned array. * * @see #getExact(int, Class) */ <T> T get(int size, Class<T> arrayClass);
/** * Returns a non-null array of the given type with a length exactly equal to the given size. * * <p>If an array of the given size isn't in the pool, a new one will be allocated. * * <p>This class makes no guarantees about the contents of the returned array. * * @see #get(int, Class) */ <T> T getExact(int size, Class<T> arrayClass);
/** * Clears all arrays from the pool. */ voidclearMemory();
/** * Trims the size to the appropriate level. * * @param level A trim specified in {@link android.content.ComponentCallbacks2}. */ voidtrimMemory(int level);
/** * Similar to {@link java.util.LinkedHashMap} when access ordered except that it is access ordered * on groups of bitmaps rather than individual objects. The idea is to be able to find the LRU * bitmap size, rather than the LRU bitmap object. We can then remove bitmaps from the least * recently used size of bitmap when we need to reduce our cache size. * * For the purposes of the LRU, we count gets for a particular size of bitmap as an access, even if * no bitmaps of that size are present. We do not count addition or removal of bitmaps as an * access. */ classGroupedLinkedMap<KextendsPoolable, V> { // head 是链表的头节点 privatefinal LinkedEntry<K, V> head = new LinkedEntry<>(); privatefinal Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();
publicvoidput(K key, V value){ LinkedEntry<K, V> entry = keyToEntry.get(key);
if (entry == null) { entry = new LinkedEntry<>(key); // 放到链表末尾 makeTail(entry); keyToEntry.put(key, entry); } else { // 不需要用到这个 key。key 是 poolable 的,offer 后会放回 pool key.offer(); }
entry.add(value); }
@Nullable public V get(K key){ LinkedEntry<K, V> entry = keyToEntry.get(key); if (entry == null) { entry = new LinkedEntry<>(key); keyToEntry.put(key, entry); } else { key.offer(); }
// 刚刚访问过的元素,要移到链表的开头 makeHead(entry);
// 如果是刚刚 new 出来的 LinkedEntry,这里会返回 null return entry.removeLast(); }
@Nullable public V removeLast(){ // LinkedEntry 组成的是一个双向的链表,head.prev 是链表的尾节点 LinkedEntry<K, V> last = head.prev;
while (!last.equals(head)) { V removed = last.removeLast(); if (removed != null) { return removed; } else { // We will clean up empty lru entries since they are likely to have been one off or // unusual sizes and // are not likely to be requested again so the gc thrash should be minimal. Doing so will // speed up our // removeLast operation in the future and prevent our linked list from growing to // arbitrarily large // sizes. removeEntry(last); keyToEntry.remove(last.key); last.key.offer(); }
last = last.prev; }
returnnull; }
@Override public String toString(){ StringBuilder sb = new StringBuilder("GroupedLinkedMap( "); LinkedEntry<K, V> current = head.next; boolean hadAtLeastOneItem = false; while (!current.equals(head)) { hadAtLeastOneItem = true; sb.append('{').append(current.key).append(':').append(current.size()).append("}, "); current = current.next; } if (hadAtLeastOneItem) { sb.delete(sb.length() - 2, sb.length()); } return sb.append(" )").toString(); }
// Make the entry the most recently used item. privatevoidmakeHead(LinkedEntry<K, V> entry){ removeEntry(entry); entry.prev = head; entry.next = head.next; updateEntry(entry); }
// Make the entry the least recently used item. privatevoidmakeTail(LinkedEntry<K, V> entry){ removeEntry(entry); entry.prev = head.prev; entry.next = head; updateEntry(entry); }
private <T> T getForKey(Key key, Class<T> arrayClass){ ArrayAdapterInterface<T> 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> T getArrayForKey(Key key){ // groupedMap 的实现我们在前面看过了 return (T) groupedMap.get(key); }