1 package com.bumptech.glide.load.engine.bitmap_recycle; 2 3 import android.annotation.SuppressLint; 4 import android.annotation.TargetApi; 5 import android.graphics.Bitmap; 6 import android.graphics.Color; 7 import android.os.Build; 8 import android.util.Log; 9 10 import java.util.Collections; 11 import java.util.HashSet; 12 import java.util.Set; 13 14 /** 15 * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an 16 * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s and then uses an LRU 17 * eviction policy to evict {@link android.graphics.Bitmap}s from the least recently used bucket in order to keep 18 * the pool below a given maximum size limit. 19 */ 20 public class LruBitmapPool implements BitmapPool { 21 private static final String TAG = "LruBitmapPool"; 22 private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888; 23 24 private final LruPoolStrategy strategy; 25 private final int initialMaxSize; 26 private final BitmapTracker tracker; 27 28 private int maxSize; 29 private int currentSize; 30 private int hits; 31 private int misses; 32 private int puts; 33 private int evictions; 34 35 // Exposed for testing only. 36 LruBitmapPool(int maxSize, LruPoolStrategy strategy) { 37 this.initialMaxSize = maxSize; 38 this.maxSize = maxSize; 39 this.strategy = strategy; 40 this.tracker = new NullBitmapTracker(); 41 } 42 43 /** 44 * Constructor for LruBitmapPool. 45 * 46 * @param maxSize The initial maximum size of the pool in bytes. 47 */ 48 public LruBitmapPool(int maxSize) { 49 this(maxSize, getDefaultStrategy()); 50 } 51 52 @Override 53 public int getMaxSize() { 54 return maxSize; 55 } 56 57 @Override 58 public synchronized void setSizeMultiplier(float sizeMultiplier) { 59 maxSize = Math.round(initialMaxSize * sizeMultiplier); 60 evict(); 61 } 62 63 @Override 64 public synchronized boolean put(Bitmap bitmap) { 65 if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize) { 66 if (Log.isLoggable(TAG, Log.VERBOSE)) { 67 Log.v(TAG, "Reject bitmap from pool=" + strategy.logBitmap(bitmap) + " is mutable=" 68 + bitmap.isMutable()); 69 } 70 return false; 71 } 72 73 final int size = strategy.getSize(bitmap); 74 strategy.put(bitmap); 75 tracker.add(bitmap); 76 77 puts++; 78 currentSize += size; 79 80 if (Log.isLoggable(TAG, Log.VERBOSE)) { 81 Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap)); 82 } 83 dump(); 84 85 evict(); 86 return true; 87 } 88 89 private void evict() { 90 trimToSize(maxSize); 91 } 92 93 @Override 94 public synchronized Bitmap get(int width, int height, Bitmap.Config config) { 95 Bitmap result = getDirty(width, height, config); 96 if (result != null) { 97 // Bitmaps in the pool contain random data that in some cases must be cleared for an image to be rendered 98 // correctly. we shouldn't force all consumers to independently erase the contents individually, so we do so 99 // here. See issue #131. 100 result.eraseColor(Color.TRANSPARENT); 101 } 102 103 return result; 104 } 105 106 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) 107 @Override 108 public synchronized Bitmap getDirty(int width, int height, Bitmap.Config config) { 109 // Config will be null for non public config types, which can lead to transformations naively passing in 110 // null as the requested config here. See issue #194. 111 final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG); 112 if (result == null) { 113 if (Log.isLoggable(TAG, Log.DEBUG)) { 114 Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config)); 115 } 116 misses++; 117 } else { 118 hits++; 119 currentSize -= strategy.getSize(result); 120 tracker.remove(result); 121 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { 122 result.setHasAlpha(true); 123 } 124 } 125 if (Log.isLoggable(TAG, Log.VERBOSE)) { 126 Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config)); 127 } 128 dump(); 129 130 return result; 131 } 132 133 @Override 134 public void clearMemory() { 135 trimToSize(0); 136 } 137 138 @SuppressLint("InlinedApi") 139 @Override 140 public void trimMemory(int level) { 141 if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) { 142 clearMemory(); 143 } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { 144 trimToSize(maxSize / 2); 145 } 146 } 147 148 private synchronized void trimToSize(int size) { 149 while (currentSize > size) { 150 final Bitmap removed = strategy.removeLast(); 151 tracker.remove(removed); 152 currentSize -= strategy.getSize(removed); 153 removed.recycle(); 154 evictions++; 155 if (Log.isLoggable(TAG, Log.DEBUG)) { 156 Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed)); 157 } 158 dump(); 159 } 160 } 161 162 private void dump() { 163 if (Log.isLoggable(TAG, Log.VERBOSE)) { 164 Log.v(TAG, "Hits=" + hits + " misses=" + misses + " puts=" + puts + " evictions=" + evictions 165 + " currentSize=" + currentSize + " maxSize=" + maxSize + "\nStrategy=" + strategy); 166 } 167 } 168 169 private static LruPoolStrategy getDefaultStrategy() { 170 final LruPoolStrategy strategy; 171 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 172 strategy = new SizeStrategy(); 173 } else { 174 strategy = new AttributeStrategy(); 175 } 176 return strategy; 177 } 178 179 private interface BitmapTracker { 180 void add(Bitmap bitmap); 181 void remove(Bitmap bitmap); 182 } 183 184 @SuppressWarnings("unused") 185 // Only used for debugging 186 private static class ThrowingBitmapTracker implements BitmapTracker { 187 private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>()); 188 189 @Override 190 public void add(Bitmap bitmap) { 191 if (bitmaps.contains(bitmap)) { 192 throw new IllegalStateException("Can't add already added bitmap: " + bitmap + " [" + bitmap.getWidth() 193 + "x" + bitmap.getHeight() + "]"); 194 } 195 bitmaps.add(bitmap); 196 } 197 198 @Override 199 public void remove(Bitmap bitmap) { 200 if (!bitmaps.contains(bitmap)) { 201 throw new IllegalStateException("Cannot remove bitmap not in tracker"); 202 } 203 bitmaps.remove(bitmap); 204 } 205 } 206 207 private static class NullBitmapTracker implements BitmapTracker { 208 @Override 209 public void add(Bitmap bitmap) { 210 // Do nothing. 211 } 212 213 @Override 214 public void remove(Bitmap bitmap) { 215 // Do nothing. 216 } 217 } 218 } 219