Home | History | Annotate | Download | only in bitmap_recycle
      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