1 package com.bumptech.glide.load.engine.bitmap_recycle; 2 3 import android.annotation.TargetApi; 4 import android.graphics.Bitmap; 5 6 import java.util.TreeMap; 7 8 /** 9 * A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int, Bitmap.Config)}. Requires KitKat 10 * (API 19) or higher. 11 */ 12 @TargetApi(19) 13 class SizeStrategy implements LruPoolStrategy { 14 private static final int MAX_SIZE_MULTIPLE = 4; 15 private final KeyPool keyPool = new KeyPool(); 16 private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<Key, Bitmap>(); 17 private final TreeMap<Integer, Integer> sortedSizes = new TreeMap<Integer, Integer>(); 18 19 @Override put(Bitmap bitmap)20 public void put(Bitmap bitmap) { 21 final Key key = keyPool.get(bitmap.getAllocationByteCount()); 22 23 groupedMap.put(key, bitmap); 24 25 Integer current = sortedSizes.get(key.size); 26 sortedSizes.put(key.size, current == null ? 1 : current + 1); 27 } 28 29 @Override get(int width, int height, Bitmap.Config config)30 public Bitmap get(int width, int height, Bitmap.Config config) { 31 final int size = getSize(width, height, config); 32 Key key = keyPool.get(size); 33 34 Integer possibleSize = sortedSizes.ceilingKey(size); 35 if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) { 36 keyPool.offer(key); 37 key = keyPool.get(possibleSize); 38 } 39 40 // 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 41 final Bitmap result = groupedMap.get(key); 42 if (result != null) { 43 result.reconfigure(width, height, config); 44 decrementBitmapOfSize(possibleSize); 45 } 46 47 return result; 48 } 49 50 @Override removeLast()51 public Bitmap removeLast() { 52 Bitmap removed = groupedMap.removeLast(); 53 if (removed != null) { 54 final int removedSize = removed.getAllocationByteCount(); 55 decrementBitmapOfSize(removedSize); 56 } 57 return removed; 58 } 59 decrementBitmapOfSize(Integer size)60 private void decrementBitmapOfSize(Integer size) { 61 Integer current = sortedSizes.get(size); 62 if (current == 1) { 63 sortedSizes.remove(size); 64 } else { 65 sortedSizes.put(size, current - 1); 66 } 67 } 68 69 @Override logBitmap(Bitmap bitmap)70 public String logBitmap(Bitmap bitmap) { 71 return getBitmapString(bitmap); 72 } 73 74 @Override logBitmap(int width, int height, Bitmap.Config config)75 public String logBitmap(int width, int height, Bitmap.Config config) { 76 return getBitmapString(getSize(width, height, config)); 77 } 78 79 @Override getSize(Bitmap bitmap)80 public int getSize(Bitmap bitmap) { 81 return bitmap.getAllocationByteCount(); 82 } 83 84 @Override toString()85 public String toString() { 86 String result = "SizeStrategy:\n " + groupedMap + "\n SortedSizes( "; 87 boolean hadAtLeastOneKey = false; 88 for (Integer size : sortedSizes.keySet()) { 89 hadAtLeastOneKey = true; 90 result += "{" + getBitmapString(size) + ":" + sortedSizes.get(size) + "}, "; 91 } 92 if (hadAtLeastOneKey) { 93 result = result.substring(0, result.length() - 2); 94 } 95 return result + " )"; 96 } 97 getBitmapString(Bitmap bitmap)98 private static String getBitmapString(Bitmap bitmap) { 99 return getBitmapString(bitmap.getAllocationByteCount()); 100 } 101 getBitmapString(int size)102 private static String getBitmapString(int size) { 103 return "[" + size + "]"; 104 } 105 getSize(int width, int height, Bitmap.Config config)106 private static int getSize(int width, int height, Bitmap.Config config) { 107 return width * height * getBytesPerPixel(config); 108 } 109 getBytesPerPixel(Bitmap.Config config)110 private static int getBytesPerPixel(Bitmap.Config config) { 111 switch (config) { 112 case ARGB_8888: 113 return 4; 114 case RGB_565: 115 return 2; 116 case ARGB_4444: 117 return 2; 118 case ALPHA_8: 119 return 1; 120 default: 121 // We only use this to calculate sizes to get, so choosing 4 bytes per pixel is conservative and 122 // probably forces us to get a larger bitmap than we really need. Since we can't tell for sure, probably 123 // better safe than sorry. 124 return 4; 125 } 126 } 127 128 private static class KeyPool extends BaseKeyPool<Key> { 129 get(int size)130 public Key get(int size) { 131 Key result = get(); 132 result.init(size); 133 return result; 134 } 135 136 @Override create()137 protected Key create() { 138 return new Key(this); 139 } 140 } 141 142 private static class Key implements Poolable { 143 private final KeyPool pool; 144 private int size; 145 Key(KeyPool pool)146 private Key(KeyPool pool) { 147 this.pool = pool; 148 } 149 init(int size)150 public void init(int size) { 151 this.size = size; 152 } 153 154 @Override equals(Object o)155 public boolean equals(Object o) { 156 if (this == o) return true; 157 if (o == null || getClass() != o.getClass()) return false; 158 159 Key key = (Key) o; 160 161 return size == key.size; 162 } 163 164 @Override hashCode()165 public int hashCode() { 166 return size; 167 } 168 169 @Override toString()170 public String toString() { 171 return getBitmapString(size); 172 } 173 174 @Override offer()175 public void offer() { 176 pool.offer(this); 177 } 178 } 179 } 180