preface

Improper Bitmap loading leads to too much memory occupation or memory jitter, which may be an unavoidable problem for every Android engineer. How to manage Bitmap memory reasonably has almost become a necessary lesson for everyone. Fortunately, Android has already provided us with a solution;

Main contents of this chapter:

  • BitmapAn introduction to memory management and reuse
  • LruBitmapPoolBasic analysis of
  • AttributeStrategyCritical logic analysis
  • SizeConfigStrategyCritical logic analysis
  • GroupedLinkedMapSource code analysis
  • MemorySizeCalculatorBasic calculation of size

Bitmap memory management and reuse

Refer to the Managing Bitmap Memory documentation:

Android memory management for Bitmaps

On Android2.2 or lower, threads are suspended when garbage collection occurs. This causes delays and reduces program performance. Android2.3 adds concurrent garbage collection, which means that memory occupied by image objects when they are no longer referenced is immediately reclaimed.

On Android2.3.3 or lower, Bitmap pixel data is stored in Native memory, separate from the Bitmap itself, which is stored in Java virtual machine heap memory. The release of pixel data stored in local memory is unpredictable and can cause an application to temporarily exceed its memory limit and crash. After Android3.0, Bitmap pixel data and itself are stored in the Java virtual machine’s heap memory. In Android8.0 and beyond, bitmaps are stored back in the Native heap.

On Android2.3.3 or lower, call the recycle() method to try to recycle a Bitmap;

In Android3.0 or higher version, use BitmapFactory. Options. InBitmap to try to reuse of Bitmap, but reuse Bitmap is conditional limit;

Restrictions on reusing bitmaps refer to official documentation

  • First, Bitmap must be mutable, allowing it to passBitmapFactory.OptionsSettings;
  • Before Android4.4, only JPG and PNG images are supported, and the size is the same, andinSampleSize=1To reuse the Bitmap, inPreferredConfig must be set to the same as the target Config.
  • After Android4.4, bitmaps can be reused as long as the memory size is not smaller than the required Bitmap.
  • Bitmap.config. HARDWARE does not need to be reused;

If you want to reuse bitmaps, you need a collection to store those bitmaps, and in Glide, the BitmapPool does just that.

In the Glide BitmapPool

BitmapPool is a Glide interface for unified management of Bitmap reuse. In principle, all operations that need to create Bitmap should be obtained through it. The class diagram of BitmapPool is as follows:

  • BitmapPool is an interface, the implementation class has BitmapPoolAdapter and LruBitmapPool these two;

  • The BitmapPoolAdapter is an empty shell that does no actual caching;

  • LruBitmapPool adopts the policy mode. It does not deal with specific logic itself. The real logic is in LruPoolStrategy.

LruBitmapPool

LruBitmapPool is the enforcer of the policy and the controller of the cache size.

LruBitmapPool.java

Public class LruBitmapPool implements BitmapPool{// construct public LruBitmapPool(long maxSize) {this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs()); } // LruBitmapPool(long maxSize, LruPoolStrategy, Set<Bitmap.Config> allowedConfigs) { this.initialMaxSize = maxSize; this.maxSize = maxSize; this.strategy = strategy; this.allowedConfigs = allowedConfigs; this.tracker = new NullBitmapTracker(); Public synchronized void put(Bitmap) {synchronized void put(Bitmap) {if (bitmap == null) {
      throw new NullPointerException("Bitmap must not be null");
    }
    if (bitmap.isRecycled()) {
      throw new IllegalStateException("Cannot pool recycled bitmap"); } // If bitmap is not Mutable, or the bitmap size is larger than the maximum size, or caching is not allowedif(! bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || ! Allowedconfigs. contains(bitmap.getConfig())) {// Recycle bitmap bitmap.recycle();return; } // Get bitmap size final int size = strategy.getSize(bitmap); strategy.put(bitmap); // Add lruCache tracker. Add (bitmap); // calculate the number of put and currentSize puts++; currentSize += size;if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Put bitmap in pool="+ strategy.logBitmap(bitmap)); } dump(); evict(); Private static Set< bitmap.config >getDefaultAllowedConfigsConfigs = new HashSet<>(arrays.aslist (bitmap.config.values ()));if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      configs.add(null);
    }
    if(build.version.sdk_int >= build.version_codes.o) {// Bitmap.config. HARDWARE is available after 8.0, Disallow caching configs.remove(bitmap.config.hardware); }returnCollections.unmodifiableSet(configs); } // Get dirty data, Private synchronized Bitmap getDirtyOrNull(int width, int height, @Nullable Bitmap.Config config) { assertNotHardwareConfig(config); // Get final Bitmap result = strategy.get(width, height, config! = null ? config : DEFAULT_CONFIG);if(result == null) {// Fail to get, number of misses+ 1; }else{ hits++; +1 currentSize -= strategy.getSize(result); / / currentSize reduce tracker. Remove (result); normalize(result); } dump();returnresult; Private synchronized void trimToSize(long size) {private synchronized void trimToSize(long size) {while(currentSize > size) {final Bitmap removed = strategy.removelast (); // Call the policy methodif (removed == null) {
        if (Log.isLoggable(TAG, Log.WARN)) {
          Log.w(TAG, "Size mismatch, resetting");
          dumpUnchecked();
        }
        currentSize = 0;
        return;
      }
      tracker.remove(removed);
      currentSize -= strategy.getSize(removed);
      evictions++;
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Evicting bitmap="+ strategy.logBitmap(removed)); } dump(); removed.recycle(); Public bitmap get(int width, int height,) public bitmap get(int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config);if(result ! = null) { result.eraseColor(Color.TRANSPARENT); }else {
      result = createBitmap(width, height, config);
    }
    returnresult; Public Bitmap getDirty(int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config);if (result == null) {
      result = createBitmap(width, height, config);
    }
    returnresult; }}Copy the code

LruBitmapPool main methods:

Constructor: you need to pass in maxSize, which is required to control the size of the cache;

Put () : The Bitmap is cached. If the cache condition is not met, the Bitmap is reclaimed.

GetDirtyOrNull () : Retrieves the Bitmap from the cache, possibly returning null;

TrimToSize () : Rearrange the memory to prevent exceeding the target size;

Get () : Get a bitmap of fully transparent pixels, not empty;

GetDirty () : Gets dirty data directly. If it is taken from the cache, it may contain dirty data and is not null.

LruPoolStrategy is also an abstract strategy interface. The real strategy implementation classes are SizeConfigStrategy and AttributeStrategy.

LruPoolStrategy

LruPoolStrategy.java

Interface LruPoolStrategy {//put Void put(Bitmap Bitmap); @nullable Bitmap get(int width, int height, bitmap.config Config); @Nullable Bitmap removeLast(); StringlogBitmap(Bitmap bitmap);

  String logBitmap(int width, int height, Bitmap.Config config);
 
  int getSize(Bitmap bitmap);
}
Copy the code

Get an instance of LruPoolStrategy by calling getDefaultStrategy() :

getDefaultStrategy()

private static LruPoolStrategy getDefaultStrategy() {
    final LruPoolStrategy strategy;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      strategy = new SizeConfigStrategy();
    } else {
      strategy = new AttributeStrategy();
    }
    return strategy;
  }
Copy the code

SizeConfigStrategy is a strategy for Android4.4 and above, and AttributeStrategy is a strategy for lower versions.

Let’s start with the lower version of the policy:

AttributeStrategy

AttributeStrategy.java

class AttributeStrategy implements LruPoolStrategy { private final KeyPool keyPool = new KeyPool(); //KeyPool private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>(); LRU cache@override public void put(Bitmap Bitmap) {// Obtain the Key final Key Key = keyPool. Get (bitmap.getwidth (), bitmap.getHeight(), bitmap.getConfig()); // Save groupedmap. put(key, bitmap); } @override public Bitmap get(int width, int height, bitmap. Config Config) {// Obtain Key final Key Key = keyPool. height, config); / / removereturngroupedMap.get(key); } // Remove the trailing element @override public BitmapremoveLast() {
    returngroupedMap.removeLast(); }}Copy the code

AttributeStrategy overrides the method defined by the interface, where GroupedLinkedMap is the set that actually implements the Lru logic. Note that the Key is retrieved in KeyPool. Key encapsulates the input parameters int width, int height, bitmap. Config Config, and Lru cache keys.

Attributestrategy.key overwrites equals() and hashCode(), where hashCode() is used to identify buckets in an Lru internal LinkedHashMap, and equal() is the real comparison;

AttributeStrategy.Key

{
    @Override
    public boolean equals(Object o) {
      if(o instanceof Key) { Key other = (Key) o; // Width, heigth, and config are equalreturn width == other.width && height == other.height && config == other.config;
      }
      return false;
    }

    @Override
    public int hashCode() {
      //hashHashCode () int result = width; result = 31 * result + height; result = 31 * result + (config ! = null ? config.hashCode() : 0);returnresult; }}Copy the code

The core purpose of AttributeStrategy is to compare cached keys. Only cached bitmaps that match width, height, and config can be matched.

SizeConfigStrategy

AttributeStrategy is for Bitmap caching strategies lower than Android4.4, and SizeConfigStrategy is for higher versions of inBitmap. At least in the size of this piece is open, only the memory size is not less than the requirements on the line; Here’s how the code works:

SizeConfigStrategy.java

public class SizeConfigStrategy implements LruPoolStrategy { private static final int MAX_SIZE_MULTIPLE = 8; private final KeyPool keyPool = new KeyPool(); private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>(); private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>(); @override public void put(Bitmap Bitmap) {int size = util.getBitmapBytesize (Bitmap); Key = keyPool. Get (size, bitmap.getconfig ()); // Save to LRU groupedmap. put(key, bitmap); SizeConfig NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig()); // sizeConfig NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig()); // Save the key-value pairs, where the key is the size of bytes and the value is the total number of sizes; Integer current = size.get (key.size); sizes.put(key.size, current == null ? 1 : current + 1); } public Bitmap get(int width, int height, bitmap.config Config) {public Bitmap get(int width, int height, bitmap.config Config) { height, config); BestKey = findBestKey(size, config); // Get Bitmap result from LRU = groupedmap.get (bestKey);if(result ! DecrementBitmapOfSize (bestkey. size, result); Config result.reconfigure(width, height, result.getconfig ()! = null ? result.getConfig() : Bitmap.Config.ARGB_8888); }returnresult; SizesForConfig private NavigableMap<Integer, Integer> getSizesForConfig(Bitmap.Config config) { NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);if (sizes == null) {
      //treeMap
      sizes = new TreeMap<>();
      sortedSizes.put(config, sizes);
    }
    returnsizes; Static final class Key{@override public Boolean equals(Object o) {if (o instanceof Key) {
        Key other = (Key) o;
        return size == other.size
            && Util.bothNullOrEqual(config, other.config);
      }
      return false;
    }

    @Override
    public int hashCode() { int result = size; result = 31 * result + (config ! = null ? config.hashCode() : 0);returnresult; }}}}Copy the code

SizeConfigStrategy and AttributeStrategy have many similarities, but much more complex, the same is that GroupedLinkedMap is used as Lru storage, the difference is the Key acquisition and a more auxiliary set NavigableMap; Key retrieves size, which is the number of bytes used by bitmaps, and Key hashCode() and equals(), which are size and config.

The Key method to SizeConfigStrategy is getBestKey(), which gets the most appropriate Key;

SizeConfigStrategy.findBestKey()

Private Key findBestKey(int size, bitmap.config Config) { Key result = keyPool. Get (size, config); // Get the matching Config, usually only one matchfor(Bitmap.Config possibleConfig : GetInConfigs (config)) {sizesForConfig NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig); / / get may cache size than the size small, between methods quite so mathematically into a Integer possibleSize = sizesForPossibleConfig. CeilingKey (size); // The hit size should not be larger than 8 times the target size.if(possibleSize ! = null && possibleSize <= size * MAX_SIZE_MULTIPLE) {// 'size' is not equal or 'config' is not equal. Key.equals() 'logic, then lower the dimensions to get similar keysif(possibleSize ! = size || (possibleConfig == null ? config ! = null : ! Possibleconfig.equals (config)) {// Accept similar cache keys and put the key created in the first step into the queue keyPool. Offer (result); Result = keyPool. Get (possibleSize, possibleConfig); }break; }}returnresult; } private static bitmap. config [] getInConfigs(bitmap.config) {private static bitmap. config [] getInConfigs(bitmap.config) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      if (Bitmap.Config.RGBA_F16.equals(requested)) {
        return RGBA_F16_IN_CONFIGS;
      }
    }
    switch (requested) {
      case ARGB_8888:
        return ARGB_8888_IN_CONFIGS;
      case RGB_565:
        return RGB_565_IN_CONFIGS;
      case ARGB_4444:
        return ARGB_4444_IN_CONFIGS;
      case ALPHA_8:
        return ALPHA_8_IN_CONFIGS;
      default:
        returnnew Bitmap.Config[] { requested }; RGBA_F16 private static final bitmap. Config[] ARGB_8888_IN_CONFIGS; static { Bitmap.Config[] result = new Bitmap.Config[] { Bitmap.Config.ARGB_8888, null, };if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { result = Arrays.copyOf(result, result.length + 1); result[result.length - 1] = Config.RGBA_F16; } ARGB_8888_IN_CONFIGS = result; } //RGBA_F16_IN_CONFIGS is the same as ARGB_8888_IN_CONFIGS in private static final bitmap. Config[] RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS; Private static final bitmap. Config[] RGB_565_IN_CONFIGS = new bitmap. Config[] {bitmap.config. RGB_565}; Private static final bitmap. Config[] ARGB_4444_IN_CONFIGS = new bitmap. Config[] {bitmap.config. ARGB_4444 }; ALPHA_8 private static final bitmap. Config[] ALPHA_8_IN_CONFIGS = new bitmap. Config[] {bitmap.config. ALPHA_8 };Copy the code

SizesForPossibleConfig getBestKey() : getInConfigs() : sizesForPossibleConfig;

  • PossibleSize must be less than or equal to size * MAX_SIZE_MULTIPLE. MAX_SIZE_MULTIPLE is 8 by default. If greater than 8, memory utilization is low and there is no need to force matching caching;

  • If sizesForPossibleConfig and possibleSize have an equal, with the target can be reused, or explain two key must be equal to (refer to the key. The equals () method), the two equal to latitude and longitude of the matching, just returned directly;

Assist the Map

Looking back at the NavigableMap, get a TreeMap by calling getSizesForConfig(). This Map holds the size of each cached Bitmap and the count of the same size. Call the ceilingKey(size) method in the getBestKey() method, TreeMap will naturally sort the key by default. The meaning of the ceilingKey(size) function is to return a key that is closest to size and not less than size, which is exactly in line with the value of memory overcommitment. while

Map = Map = Map = Map = Map = Map = Map = Map = Map = Map

DecrementBitmapOfSize () is a method in SizeConfigStrategy;

private void decrementBitmapOfSize(Integer size, Bitmap removed) {
    Bitmap.Config config = removed.getConfig();
    NavigableMap<Integer, Integer> sizes = getSizesForConfig(config);
    Integer current = sizes.get(size);
    if (current == null) {
    }
    if(current == 1) {// Size. remove(size); }else{// subtract 1 sizes. Put (size, current-1); }}Copy the code

This method is called when a Bitmap is pulled or removed from the cache pool and executes: After operation on this map, the size corresponding to the removed Bitmap is reduced by 1 or the current key is removed. Only after removal, we can know whether the size exists in the cache when ceilingKey(size) is called by getBestKey().

GroupedLinkedMap

BitmapPool really implements LruCache function is GroupedLinkedMap, the function of this class is very similar to LinkedHashMap but different, the same is the use of linked list to remember the order of data access, the difference is that this class saves the value of the same key into an array;

Class GroupedLinkedMap<K extends Poolable, V> {class GroupedLinkedMap<K extends Poolable, V> {class GroupedLinkedMap<K extends Poolable, V> { Private static class LinkedEntry<K, V> {@synthetic final key; //key private List<V> values; // Value array LinkedEntry<K, V> next; // LinkedEntry<K, V> prev; // a node on the list // constructLinkedEntry() { this(null); } // construct LinkedEntry(K key) {next = prev = this; this.key = key; } // Remove the last element of the array @nullable public VremoveLast() {
      final int valueSize = size();
      returnvalueSize > 0 ? values.remove(valueSize - 1) : null; } // The length of the array public intsize() {
      returnvalues ! = null ? values.size() : 0; Public void add(V value) {if(values == null) { values = new ArrayList<>(); } values.add(value); Private final LinkedEntry<K, V> head = new LinkedEntry<>(); Private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>(); Public void put(K key, V value) {LinkedEntry<K, V> entry = keyToentry.get (key);if(Entry == null) {// Create a node entry = new LinkedEntry<>(key); MakeTail (entry); / / on thehashThe Map of keyToEntry. Put (key, entry); }else{ key.offer(); //keyPool operation} // Put entry into the entry array. Add (value); } public V get(K key) {// Find LinkedEntry<K, V> entry = keyToentry.get (key);if(Entry == null) {// If it does not exist, create a node and place it inhashEntry = new LinkedEntry<>(key); keyToEntry.put(key, entry); }else{ key.offer(); //keyPool operation} // Put the list header makeHead(entry);returnentry.removeLast(); Private void makeHead(LinkedEntry<K, V> entry) {removeEntry(entry); entry.prev = head; entry.next = head.next; updateEntry(entry); } private void makeTail(LinkedEntry<K, V> entry) {// removeEntry(entry); // Bind the relationship entry.prev = head.prev; entry.next = head; // Bind the relationship between the front and back nodes and itself updateEntry(entry); } // Update the node with the last next of the current node pointing to itself and the next perv pointing to itself, Private static <K, V> void updateEntry(LinkedEntry<K, V> entry) {entry.next-prev = entry; entry.prev.next = entry; } // Delete the current node and point to the next node. Private static <K, V> void removeEntry(LinkedEntry<K, V> entry) {entry.prev.next = entry.next; entry.next.prev = entry.prev; } // Remove the element at the end of the queue public VremoveLast<K, V> last = head.prev; // Whild loopwhile(! Last.equals(head)) {last.equals(head) = last.removelast ();if(removed ! = null) {// Return if not nullreturn removed;
      } elseRemoveEntry (last) {// The last node is empty, so no data is removed at all. keyToEntry.remove(last.key); last.key.offer(); } // The last node is empty, so continue to explore the last node until you canreturnGo out = go out; }returnnull; }}Copy the code

The amount of code of GroupedLinkedMap is not large. I have made detailed notes in the code. If there is any improper explanation, please leave a message for communication.

BitmapPool Calculation of the cache size

First, BitmapPool is a singleton relative to Glide, created in glideBuilder.build (), and passed maxSize in the constructor. MaxSize calculation rules from MemorySizeCalculator. GetBitmapPoolSize ();

GlideBuilder

/ / BitmapPool creationif(bitmapPool = = null) {/ / obtained through memorySizeCalculator bitmapPoolSize int size = memorySizeCalculator. GetBitmapPoolSize ();if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
      } else{ bitmapPool = new BitmapPoolAdapter(); }}Copy the code

Get size via memorySizeCalculator. If size equals 0, create BitmapPoolAdapter, otherwise create LruBitmapPool. When is size equal to 0? Let’s look at some definitions of memorySizeCalculator;

MemorySizeCalculator

/ / construction method, the Builder pattern MemorySizeCalculator (MemorySizeCalculator. Builder Builder) {enclosing context = Builder. The context; ArrayPoolSize = isLowMemoryDevice(Builder.activityManager)? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR : builder.arrayPoolSizeBytes; / / maximum total memory cache size int maxSize = getMaxSize (builder activityManager, builder maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier); / / screen width int widthPixels = builder. ScreenDimensions. GetWidthPixels (); / / screen height int heightPixels = builder. ScreenDimensions. GetHeightPixels (); Int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL; / / target bitmap cache pool Size int targetBitmapPoolSize = math.h round (screenSize * builder. BitmapPoolScreens); / / target memory cache size int targetMemoryCacheSize = math.h round (screenSize * builder. MemoryCacheScreens); // Available memory size int availableSize = maxSize -arraypoolsize; // If the sum of the calculated size is less than the available memory, directly assign the valueif (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
      memoryCacheSize = targetMemoryCacheSize;
      bitmapPoolSize = targetBitmapPoolSize;
    } else{// Redistribute memoryCacheSize and bitmapPoolSize proportionedfloatpart = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens); memoryCacheSize = Math.round(part * builder.memoryCacheScreens); bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens); }}Copy the code

First, MemorySizeCalculator is Builder pattern, the main parameters is in MemorySizeCalculator. The Builder to generate, in MemorySizeCalculator construction method for the calculation of Glide all memory cache, This includes the size of the arrayPool cache, the size of the bitmapPool cache, and the size of the memoryCache.

We mainly discuss BitmapPool size calculation, in the constructor, targetBitmapPoolSize calculation rule is the screen size of pixel size * builder in bitmapPoolScreens; Secondly, MemorySizeCalculator can obtain the maximum cache capacity maxSize according to the configuration of Builder. Finally, the targetBitmapPoolSize is recalculated so that it does not exceed the maximum capacity;

Look at the next MemorySizeCalculator. Builder of bitmapPoolScreens calculation:

MemorySizeCalculator.Builder

public static final class Builder { static final int BITMAP_POOL_TARGET_SCREENS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4:1;float bitmapPoolScreens = BITMAP_POOL_TARGET_SCREENS;

public Builder(Context context) {
      this.context = context;
      activityManager =
          (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      screenDimensions =
          new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics());

      // On Android O+ Bitmaps are allocated natively, ART is much more efficient at managing
      // garbage and we rely heavily on HARDWARE Bitmaps, making Bitmap re-use much less important.
      // We prefer to preserve RAM on these devices and take the small performance hit of not
      // re-using Bitmaps and textures when loading very small images or generating thumbnails.
      if(build.version.sdk_int >= build.version_codes. O && isLowMemoryDevice(activityManager)) {// On Android O, Low memory phones bitmapPoolScreens = 0 bitmapPoolScreens = 0; }}}Copy the code

BitmapPoolScreens values are on the following three occasions:

  • If the device size is smaller than Android O, the value is 4.
  • If the device is greater than or equal to Android O, low memory mobile phone, the value is 0.
  • If the device is larger than or equal to Android O, the value is 1.

It is not important to cache Bitmap memory in Native ART machines, and we can set bitmapConfig. HARDWARE.

conclusion

With the evolution of Android versions of Bitmap, Glide is also constantly adapt to the new features, high version of the reuse of Bitmap is also constantly relaxed, perhaps one day, we are no longer troubled by the Bitmap memory problem, is it possible to give up the heady Pool;