I am using the below LRU class to manage bitmaps, using these two classes I am creating and removing bitmaps to put them on viewpager views. The same code is working on below 4.2 versions but when I load my app on 4.2 or above that than the heap size is continuously increasing as I switch the pages. The problem is that I am able to recycle the bitmap in 4.2 or above but instead of that the heap size is increasing. Is their any other way to release the heap size in 4.2 or above. Please help me to solve this issue
public class LruMemoryCache extends LruSoftCache<String, Bitmap> {
private String TAG = "LruMemoryCache";
public LruMemoryCache(int size) {
super(size);
}
@Override
protected Bitmap create(Context con, String key) {
// Log.d(TAG,"######----create bitmap-----############"+size());
try {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
return BitmapFactory.decodeFile(key, options);
// return BitmapFactory.decodeFile(key);
} catch (OutOfMemoryError e) {
Log.d(TAG, "######---- bitmap Handled-----############");
Utility.freeMemory();
}
return null;
// return
// ShrinkBitmap(key,ViewMagazineActivity.Width,ViewMagazineActivity.Height);
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue,
Bitmap newValue) {
if (!evicted) {
if (oldValue != null) {
Log.v(TAG, "============----IMage is recycled-----==========="
+ key);
oldValue.recycle();
oldValue = null;
}
Utility.freeMemory();
}
}
@TargetApi(12)
protected int sizeOf(String key, Bitmap value) {
if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) >= 12) {
Log.e(TAG, "Memory occupy by bitmap-------" + value.getByteCount()
/ 131072);
return value.getByteCount();
} else {
Log.e(TAG, "Memory occupy by bitmap-------"
+ (value.getRowBytes() * value.getHeight()) / 131072);
return (value.getRowBytes() * value.getHeight());
}
}
}
public class LruSoftCache<K, V> {
private static final String LOG_TAG = LruSoftCache.class.getSimpleName();
private final LinkedHashMap<K, SoftReference<V>> map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size;
private int maxSize;
private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;
/**
* @param maxSize
* for caches that do not override {@link #sizeOf}, this is the
* maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this
* cache.
*/
public LruSoftCache(int maxSize) {
Log.v("LruSoftCache", "Constructor called");
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, SoftReference<V>>(0, 0.75f, true);
}
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
public final V get(Context context, K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Log.v("LruSoftCache", "-----GET Method is called" + key);
SoftReference<V> mapValue;
V mapReferent = null;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
mapReferent = mapValue.get();
}
if (mapReferent != null) {
hitCount++;
return mapReferent;
}
if (mapValue != null) {
size -= safeSizeOf(key, null);
}
map.remove(key);
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map may
* be different when create() returns. If a conflicting value was added
* to the map while create() was working, we leave that value in the map
* and release the created value.
*/
V createdReferent = create(context, key);
if (createdReferent == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, new SoftReference<V>(createdReferent));
if (mapValue != null) {
mapReferent = mapValue.get();
}
if (mapValue != null && mapReferent != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdReferent);
}
}
if (mapValue != null && mapReferent != null) {
entryRemoved(false, key, createdReferent, mapReferent);
return mapReferent;
} else {
trimToSize(maxSize);
return createdReferent;
}
}
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V referent) {
if (key == null || referent == null) {
throw new NullPointerException("key == null || value == null");
}
SoftReference<V> value = new SoftReference<V>(referent);
SoftReference<V> previousValue;
V previousReferent = null;
synchronized (this) {
putCount++;
size += safeSizeOf(key, referent);
previousValue = map.put(key, value);
if (previousValue != null) {
previousReferent = previousValue.get();
size -= safeSizeOf(key, previousReferent);
}
}
if (previousValue != null) {
entryRemoved(false, key, previousReferent, referent);
}
trimToSize(maxSize);
return previousReferent;
}
/**
* @param maxSize
* the maximum size of the cache before returning. May be -1 to
* evict even 0-sized elements.
*/
private void trimToSize(int maxSize) {
while (true) {
K key;
SoftReference<V> value;
V referent;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
size = 0;
map.clear();
Log.e(LOG_TAG,
getClass().getName()
+ ".sizeOf() is reporting inconsistent results! size: "
+ size + ", maxSize: " + maxSize);
break;
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<K, SoftReference<V>> toEvict = map.entrySet()
.iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
referent = (value != null) ? value.get() : null;
map.remove(key);
size -= safeSizeOf(key, referent);
evictionCount++;
}
entryRemoved(true, key, referent, null);
}
}
/**
* Removes the entry for {@code key} if it exists.
*
* @return the previous value mapped by {@code key}.
*/
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
SoftReference<V> previousValue;
V previousReferent = null;
if (map.containsKey(key)) {
synchronized (this) {
previousValue = map.remove(key);
if (previousValue != null) {
previousReferent = previousValue.get();
size -= safeSizeOf(key, previousReferent);
}
}
if (previousValue != null) {
entryRemoved(false, key, previousReferent, null);
}
}
return previousReferent;
}
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
*
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* @param evicted
* true if the entry is being removed to make space, false if the
* removal was caused by a {@link #put} or {@link #remove}.
* @param newValue
* the new value for {@code key}, if it exists. If non-null, this
* removal was caused by a {@link #put}. Otherwise it was caused
* by an eviction or a {@link #remove}.
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null.
*
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* <p>
* If a value for {@code key} exists in the cache when this method returns,
* the created value will be released with {@link #entryRemoved} and
* discarded. This can occur when multiple threads request the same key at
* the same time (causing multiple values to be created), or when one thread
* calls {@link #put} while another is creating a value for the same key.
*/
protected V create(Context context, K key) {
return null;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "="
+ value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size is
* the number of entries and max size is the maximum number of entries.
*
* <p>
* An entry's size must not change while it is in the cache.
*/
private final int sizeOf(K key, V value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted.
*/
public synchronized final int evictionCount() {
return evictionCount;
}
@Override
public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String
.format("LruCache[size=%d,mapSize=%d,maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
size, map.size(), maxSize, hitCount, missCount,
hitPercent);
}
}
I had the same problem. Recycle() used to work fine for my bitmap heavy application but now doesn't seem to release the memory even when I leave the activity that they were displayed in.
I solved it by keeping a handle on all the imageViews that I displayed bitmaps in, and now when I leave the activity I not only recycle() the bitmaps I also do a setImageBitmap(null) on all the views that were displaying those bitmaps.
I suspect that recycle() used to reclaim the memory even if something was still referencing the bitmap, but it no longer does that.