1 package com.bumptech.glide.load.engine.bitmap_recycle; 2 3 import android.annotation.TargetApi; 4 import android.graphics.Bitmap; 5 import android.os.Build; 6 7 import com.bumptech.glide.util.Util; 8 9 import java.util.TreeMap; 10 11 /** 12 * A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int, Bitmap.Config)}. 13 * Requires {@link Build.VERSION_CODES#KITKAT KitKat} (API {@value Build.VERSION_CODES#KITKAT}) or higher. 14 */ 15 @TargetApi(Build.VERSION_CODES.KITKAT) 16 class SizeStrategy implements LruPoolStrategy { 17 private static final int MAX_SIZE_MULTIPLE = 8; 18 private final KeyPool keyPool = new KeyPool(); 19 private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<Key, Bitmap>(); 20 private final TreeMap<Integer, Integer> sortedSizes = new PrettyPrintTreeMap<Integer, Integer>(); 21 22 @Override 23 public void put(Bitmap bitmap) { 24 int size = Util.getBitmapByteSize(bitmap); 25 final Key key = keyPool.get(size); 26 27 groupedMap.put(key, bitmap); 28 29 Integer current = sortedSizes.get(key.size); 30 sortedSizes.put(key.size, current == null ? 1 : current + 1); 31 } 32 33 @Override 34 public Bitmap get(int width, int height, Bitmap.Config config) { 35 final int size = Util.getBitmapByteSize(width, height, config); 36 Key key = keyPool.get(size); 37 38 Integer possibleSize = sortedSizes.ceilingKey(size); 39 if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) { 40 keyPool.offer(key); 41 key = keyPool.get(possibleSize); 42 } 43 44 // Do a get even if we know we don't have a bitmap so that the key moves to the front in the lru pool 45 final Bitmap result = groupedMap.get(key); 46 if (result != null) { 47 result.reconfigure(width, height, config); 48 decrementBitmapOfSize(possibleSize); 49 } 50 51 return result; 52 } 53 54 @Override 55 public Bitmap removeLast() { 56 Bitmap removed = groupedMap.removeLast(); 57 if (removed != null) { 58 final int removedSize = Util.getBitmapByteSize(removed); 59 decrementBitmapOfSize(removedSize); 60 } 61 return removed; 62 } 63 64 private void decrementBitmapOfSize(Integer size) { 65 Integer current = sortedSizes.get(size); 66 if (current == 1) { 67 sortedSizes.remove(size); 68 } else { 69 sortedSizes.put(size, current - 1); 70 } 71 } 72 73 @Override 74 public String logBitmap(Bitmap bitmap) { 75 return getBitmapString(bitmap); 76 } 77 78 @Override 79 public String logBitmap(int width, int height, Bitmap.Config config) { 80 int size = Util.getBitmapByteSize(width, height, config); 81 return getBitmapString(size); 82 } 83 84 @Override 85 public int getSize(Bitmap bitmap) { 86 return Util.getBitmapByteSize(bitmap); 87 } 88 89 @Override 90 public String toString() { 91 return "SizeStrategy:\n " 92 + groupedMap + "\n" 93 + " SortedSizes" + sortedSizes; 94 } 95 96 private static class PrettyPrintTreeMap<K, V> extends TreeMap<K, V> { 97 @Override 98 public String toString() { 99 StringBuilder sb = new StringBuilder(); 100 sb.append("( "); 101 for (Entry<K, V> entry : entrySet()) { 102 sb.append('{').append(entry.getKey()).append(':').append(entry.getValue()).append("}, "); 103 } 104 final String result; 105 if (!isEmpty()) { 106 result = sb.substring(0, sb.length() - 2); 107 } else { 108 result = sb.toString(); 109 } 110 return result + " )"; 111 } 112 } 113 114 private static String getBitmapString(Bitmap bitmap) { 115 int size = Util.getBitmapByteSize(bitmap); 116 return getBitmapString(size); 117 } 118 119 private static String getBitmapString(int size) { 120 return "[" + size + "]"; 121 } 122 123 // Visible for testing. 124 static class KeyPool extends BaseKeyPool<Key> { 125 126 public Key get(int size) { 127 Key result = get(); 128 result.init(size); 129 return result; 130 } 131 132 @Override 133 protected Key create() { 134 return new Key(this); 135 } 136 } 137 138 // Visible for testing. 139 static final class Key implements Poolable { 140 private final KeyPool pool; 141 private int size; 142 143 Key(KeyPool pool) { 144 this.pool = pool; 145 } 146 147 public void init(int size) { 148 this.size = size; 149 } 150 151 @Override 152 public boolean equals(Object o) { 153 if (o instanceof Key) { 154 Key other = (Key) o; 155 return size == other.size; 156 } 157 return false; 158 } 159 160 @Override 161 public int hashCode() { 162 return size; 163 } 164 165 @Override 166 public String toString() { 167 return getBitmapString(size); 168 } 169 170 @Override 171 public void offer() { 172 pool.offer(this); 173 } 174 } 175 } 176