package gws.grottworkshop.gwsholmeswatson.cache; import java.lang.ref.SoftReference; import java.util.LinkedHashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.content.Context; // TODO: Auto-generated Javadoc /** * Derived fromAOSP copy of LruCache. * * Priro to Android 3.0 bitmaps were stored on boht native heap and Dalvik VM heap with * the largest amount of data on the Dalvik VM. Thus the use case to use softreference for * both android 2.x and android past 2.x is about making sure not only that bitmaps do not stay * around in Dalvik VM longer than they should but also to make sure that smal part of data * in the native heap is not staying around in long time periods. * * * * Reference Background: * * Weak reference is not strong enough to rmeain inmemory and gets removed on next * GC cycle. Its enqueued when VM knows it a weak reference. * * Soft reference sticks aroud more than one GC cycle. Its enqueued when Vmreaches low memory. * * So for performance to span both Android 2.x and Android 3.x devices in terms of UI * speed, etc we should be using a two level cache with both a SoftLruCache and a SoftDiskLruCache. * * Because we are using a soft reference on bitmaps that other data in native heap * will eventually be de-referenced(no not the right word but hey) at one of the * GC cycles and thus than native heapstuff will be able to free it up at that point. * Still run an ondetachedfromWindow() method handler to handle or hint to * GC to do that though. * * Note: * * This still does not handel the other performance hit ie inflating full xmls instead of say using * custom view factories. Yes I amsure FB is not using this trick in the their home app:). * * And obiovusly the other perfromacne trick is to in the ImageView: * <code> * * @param <K> the key type * @param <V> the value type * @Override * protected void onDetachedFromWindow () { * setImageDrawable(null); * setBackgroundDrawable(null); * System.gc(); * } * </code> * @author fredgrott */ public class LruSoftCache<K, V> { /** The map. */ private final LinkedHashMap<K, SoftReference<V>> map; /** The gwslog. */ private Logger GWSLOG = LoggerFactory.getLogger(LruSoftCache.class); /** Size of this cache in units. Not necessarily the number of elements. */ private int size; /** The max size. */ private int maxSize; /** The put count. */ private int putCount; /** The create count. */ private int createCount; /** The eviction count. */ private int evictionCount; /** The hit count. */ private int hitCount; /** The miss count. */ private int missCount; /** * Instantiates a new lru soft cache. * * @param maxSize the max size */ public LruSoftCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, SoftReference<V>>(0, 0.75f, true); } /** * Gets the. * * @param context the context * @param key the key * @return the v */ public final V get(Context context, K key) { if (key == null) { throw new NullPointerException("key == null"); } 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)); 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; } } /** * Put. * * @param key the key * @param referent the referent * @return the v */ 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; } /** * Trim to size. * * @param maxSize the max size */ 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(); GWSLOG.error( ".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. * * @param key the key * @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; 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; } /** * Entry removed. * * @param evicted the evicted * @param key the key * @param oldValue the old value * @param newValue the new value */ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { } /** * Creates the. * * @param context the context * @param key the key * @return the v */ protected V create(Context context, K key) { return null; } /** * Safe size of. * * @param key the key * @param value the value * @return the int */ private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } /** * Size of. * * @param key the key * @param value the value * @return the int */ private final int sizeOf(K key, V value) { return 1; } /** * Evict all. */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** * Size. * * @return the int */ public synchronized final int size() { return size; } /** * Max size. * * @return the int */ public synchronized final int maxSize() { return maxSize; } /** * Hit count. * * @return the int */ public synchronized final int hitCount() { return hitCount; } /** * Miss count. * * @return the int */ public synchronized final int missCount() { return missCount; } /** * Creates the count. * * @return the int */ public synchronized final int createCount() { return createCount; } /** * Put count. * * @return the int */ public synchronized final int putCount() { return putCount; } /** * Eviction count. * * @return the int */ public synchronized final int evictionCount() { return evictionCount; } /** * To string. * * @return the string * @see java.lang.Object#toString() */ @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); } }