Created
December 7, 2012 16:54
-
-
Save kevindmoore/4234603 to your computer and use it in GitHub Desktop.
Universal ImageLoader Recycling Memory classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.radiumone.viame_android.memory; | |
import android.graphics.Bitmap; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.widget.ImageView; | |
import java.lang.ref.Reference; | |
import java.lang.ref.WeakReference; | |
/** | |
* Class for storing cache info | |
*/ | |
public class CacheItem implements Comparable<CacheItem> { | |
protected String id; | |
protected Reference<Bitmap> bitmap; | |
protected long lastAccessed; | |
protected boolean removable = true; | |
protected Reference<ImageView> imageView; | |
public CacheItem(String id, Bitmap bitmap) { | |
this.id = id; | |
setBitmap(bitmap); | |
lastAccessed = System.currentTimeMillis(); | |
} | |
public Bitmap getBitmap() { | |
return bitmap == null ? null : bitmap.get(); | |
} | |
public void setBitmap(Bitmap bitmap) { | |
this.bitmap = new WeakReference<Bitmap>(bitmap); | |
} | |
/** | |
* Remove the bitmap from the cache. Recycle & clear resources | |
*/ | |
public void removeBitmap() { | |
Bitmap bitmap = getBitmap(); | |
if (bitmap != null) { | |
if (!bitmap.isRecycled()) { | |
bitmap.recycle(); | |
} | |
setBitmap(null); | |
ImageView imageView = getImageView(); | |
if (imageView != null) { | |
// Make sure that the imageView's bitmap is the same as this one. | |
Drawable drawable = imageView.getDrawable(); | |
if (drawable instanceof BitmapDrawable) { | |
Bitmap imageViewBitmap = ((BitmapDrawable) drawable).getBitmap(); | |
if (imageViewBitmap == bitmap) { | |
drawable.setCallback(null); | |
// imageView.setImageBitmap(null); | |
imageView.setImageDrawable(null); | |
imageView.getResources().flushLayoutCache(); | |
imageView.destroyDrawingCache(); | |
} | |
} | |
} | |
} | |
} | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public long getLastAccessed() { | |
return lastAccessed; | |
} | |
public void setLastAccessed(long lastAccessed) { | |
this.lastAccessed = lastAccessed; | |
} | |
public boolean isRemovable() { | |
return removable; | |
} | |
public void setRemovable(boolean removable) { | |
this.removable = removable; | |
} | |
public ImageView getImageView() { | |
return imageView == null ? null: imageView.get(); | |
} | |
public void setImageView(ImageView imageView) { | |
this.imageView = new WeakReference<ImageView>(imageView); | |
} | |
public void updateLastAccessed() { | |
lastAccessed = System.currentTimeMillis(); | |
} | |
@Override | |
public int compareTo(CacheItem another) { | |
return id.compareTo(another.getId()); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) { | |
return true; | |
} | |
if (o == null || getClass() != o.getClass()) { | |
return false; | |
} | |
CacheItem cacheItem = (CacheItem) o; | |
if (id != null ? !id.equals(cacheItem.id) : cacheItem.id != null) { | |
return false; | |
} | |
return true; | |
} | |
@Override | |
public int hashCode() { | |
return id != null ? id.hashCode() : 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Check to see if we have enough memory to allocate a new bitmap. | |
* @return true if we can allocate a new bitmap | |
*/ | |
public boolean checkMemory() { | |
long nativeHeapAllocatedSize = Debug.getNativeHeapAllocatedSize(); | |
long totalMemory = Runtime.getRuntime().totalMemory(); | |
long totalRemainingMemory = Runtime.getRuntime().freeMemory(); | |
long currentAllocatedHeapSize = nativeHeapAllocatedSize; | |
long totalUsed = totalMemory - totalRemainingMemory - nativeHeapAllocatedSize; | |
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { | |
currentAllocatedHeapSize = totalUsed; | |
} | |
Logger.debug(this, "totalRemainingMemory Size: " + ImageUtils.getPrettyAmount(totalUsed)); | |
if ((currentAllocatedHeapSize + memoryPadding) >= systemMaxMemory) { | |
Logger.debug(this, "Allocated memory bigger than Max"); | |
return false; | |
} | |
return true; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
memoryCache = new LFUMemoryCache(CACHE_SIZE); | |
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) | |
.threadPoolSize(3) | |
.threadPriority(Thread.NORM_PRIORITY - 2) | |
// .enableLogging() | |
.memoryCache(memoryCache) | |
.defaultDisplayImageOptions(options) | |
.memoryCacheSize(CACHE_SIZE) // 1.5 Mb | |
.discCacheFileNameGenerator(new Md5FileNameGenerator()) | |
.build(); | |
// Initialize ImageLoader with configuration. | |
ImageLoader.getInstance().init(config); | |
mImageLoader = ImageLoader.getInstance(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Call the current image loader to display an image | |
* @param uri | |
* @param imageView | |
* @param listener | |
*/ | |
public void displayImage(String uri, ImageView imageView, ImageLoadingListener listener) { | |
if (!checkMemory()) { | |
memoryCache.clearMemory(memoryPadding); | |
System.gc(); | |
} | |
if (checkMemory()) { | |
mImageLoader.displayImage(uri, imageView, options, new ViaMeImageLoadingListener(imageView, listener)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Wrapper Listener to keep track of bitmap & imageview | |
*/ | |
class ImageLoadingListener implements ImageLoadingListener { | |
Reference<ImageView> imageView; | |
ImageLoadingListener listener; | |
ViaMeImageLoadingListener(ImageView imageView, ImageLoadingListener listener) { | |
this.imageView = new WeakReference<ImageView>(imageView); | |
this.listener = listener; | |
} | |
@Override | |
public void onLoadingStarted() { | |
if (listener != null) { | |
listener.onLoadingStarted(); | |
} | |
} | |
@Override | |
public void onLoadingFailed(FailReason failReason) { | |
if (listener != null) { | |
listener.onLoadingFailed(failReason); | |
} | |
} | |
@Override | |
public void onLoadingComplete(Bitmap loadedImage) { | |
if (memoryCache != null) { | |
memoryCache.setImageView(loadedImage, imageView.get()); | |
} | |
if (listener != null) { | |
listener.onLoadingComplete(loadedImage); | |
} | |
} | |
@Override | |
public void onLoadingCancelled() { | |
if (listener != null) { | |
listener.onLoadingCancelled(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.radiumone.viame_android.memory; | |
import android.graphics.Bitmap; | |
import android.os.Handler; | |
import android.widget.ImageView; | |
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache; | |
import com.radiumone.viame_android.util.log.Logger; | |
import java.lang.ref.Reference; | |
import java.lang.ref.WeakReference; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.TreeMap; | |
import java.util.concurrent.ConcurrentHashMap; | |
/** | |
* Memory class to handle recycling bitmaps. | |
* This uses a Least Frequently Used(LFU) timestamp. | |
*/ | |
public class LFUMemoryCache extends LimitedMemoryCache<String, Bitmap> { | |
protected Map<String, CacheItem> bitmapCache = new ConcurrentHashMap<String, CacheItem>(); | |
protected static TreeMap<Long,String> bitmapKeyCache = new TreeMap<Long,String>(); | |
private boolean debugging = false; | |
private final Handler handler; | |
/** | |
* Constructor. | |
* @param sizeLimit | |
*/ | |
public LFUMemoryCache(int sizeLimit) { | |
super(sizeLimit); | |
Logger.setDebug(getClass().getSimpleName(), debugging); | |
handler = new Handler(); | |
} | |
/** | |
* Add a new cache item with the given key | |
* @param key | |
* @param bitmap | |
* @return | |
*/ | |
@Override | |
public boolean put(String key, Bitmap bitmap) { | |
Logger.debug(this, "LFUMemoryCache:put key: " + key); | |
CacheItem cacheItem = new CacheItem(key, bitmap); | |
bitmapCache.put(key, cacheItem); | |
addKeyCache(cacheItem.lastAccessed, key); | |
return super.put(key, bitmap); | |
} | |
/** | |
* Get the bitmap for the given key | |
* @param key | |
* @return Bitmap | |
*/ | |
@Override | |
public Bitmap get(String key) { | |
Logger.debug(this, "LFUMemoryCache:get key: " + key); | |
Bitmap value = super.get(key); | |
// Increment last accessed | |
if (value != null) { | |
CacheItem cacheItem = bitmapCache.get(key); | |
if (cacheItem != null) { | |
long lastAccessed = cacheItem.getLastAccessed(); | |
cacheItem.updateLastAccessed(); | |
updateCacheKey(lastAccessed, cacheItem.getLastAccessed(), key); | |
} | |
} | |
return value; | |
} | |
/** | |
* Remove the cache item for the given key | |
* @param key | |
*/ | |
@Override | |
public void remove(String key) { | |
Logger.debug(this, "LFUMemoryCache:remove key: " + key); | |
CacheItem cacheItem = bitmapCache.remove(key); | |
cacheItem.removeBitmap(); | |
removeCachedKey(cacheItem.lastAccessed); | |
super.remove(key); | |
} | |
/** | |
* Get the key for the given bitmap | |
* @param bitmap | |
* @return key | |
*/ | |
protected String getKey(Bitmap bitmap) { | |
Iterator<String> iterator = bitmapCache.keySet().iterator(); | |
while (iterator.hasNext()) { | |
String key = iterator.next(); | |
CacheItem cacheItem = bitmapCache.get(key); | |
if (cacheItem.getBitmap() == bitmap) { | |
return key; | |
} | |
} | |
Logger.error("LFUMemoryCache:getKey Could not find key for bitmap : " + bitmap); | |
return null; | |
} | |
/** | |
* Clear memory of the given size | |
* @param size | |
*/ | |
public void clearMemory(long size) { | |
long totalSize = size; | |
Logger.debug(this, "LFUMemoryCache:clearMemory"); | |
while (totalSize > 0) { | |
Long lastAccessed = null; | |
String lastAccessedId = null; | |
CacheItem cachedItemTobeRemoved = null; | |
synchronized (this) { | |
if (bitmapKeyCache.size() == 0) { | |
Logger.debug(this, "LFUMemoryCache:clearMemory. bitmapKeyCache size is zero"); | |
return; | |
} | |
lastAccessed = bitmapKeyCache.firstKey(); | |
lastAccessedId = bitmapKeyCache.get(lastAccessed); | |
cachedItemTobeRemoved = bitmapCache.get(lastAccessedId); | |
} | |
if (cachedItemTobeRemoved != null) { | |
Bitmap bitmap = cachedItemTobeRemoved.getBitmap(); | |
if (bitmap != null && !bitmap.isRecycled()) { | |
Logger.debug(this, "LFUMemoryCache:clearMemory. Bitmap found " + bitmap); | |
int bitmapSize = getSize(bitmap); | |
totalSize -= bitmapSize; | |
Logger.debug(this, "LFUMemoryCache:clearMemory. Bitmap size " + bitmapSize + " Total: " + totalSize); | |
remove(cachedItemTobeRemoved.getId()); | |
removeBitmapFromViews(bitmap); | |
} | |
} | |
} | |
} | |
/** | |
* Go through our list of cache items and if they are pointing to the same bitmap, remove them from the imageview | |
* @param bitmap | |
*/ | |
private void removeBitmapFromViews(Bitmap bitmap) { | |
Logger.debug(this, "LFUMemoryCache:removeBitmapFromViews"); | |
Iterator<String> iterator = bitmapCache.keySet().iterator(); | |
List<CacheItem> itemsToRemove = null; | |
while (iterator.hasNext()) { | |
String key = iterator.next(); | |
CacheItem cacheItem = bitmapCache.get(key); | |
if (bitmap == cacheItem.getBitmap()) { | |
Logger.debug(this, "LFUMemoryCache:removeBitmapFromViews. Removing bitmap for key " + key); | |
if (itemsToRemove == null) { | |
itemsToRemove = new ArrayList<CacheItem>(); | |
} | |
itemsToRemove.add(cacheItem); | |
} | |
} | |
if (itemsToRemove != null) { | |
for (CacheItem cacheItem : itemsToRemove) { | |
remove(cacheItem.getId()); | |
} | |
} | |
} | |
/** | |
* Clear all bitmaps | |
*/ | |
@Override | |
public void clear() { | |
Logger.debug(this, "LFUMemoryCache:clear"); | |
handler.post(new Runnable() { | |
@Override | |
public void run() { | |
// Make a copy of the keys as we will be removing them 1 at a time | |
int size = bitmapCache.size(); | |
List<String> keys = new ArrayList<String>(size); | |
Iterator<String> iterator = bitmapCache.keySet().iterator(); | |
while (iterator.hasNext()) { | |
String key = iterator.next(); | |
keys.add(key); | |
} | |
// Let remove do all the work | |
for (String key : keys) { | |
remove(key); | |
} | |
LFUMemoryCache.super.clear(); | |
} | |
}); | |
} | |
/** | |
* Update the access time for the given id | |
* @param lastAccessed | |
* @param updatedAccess | |
* @param id | |
*/ | |
protected void updateCacheKey(long lastAccessed, long updatedAccess, String id) { | |
Logger.debug(this, "LFUMemoryCache:updateCacheKey id: " + id); | |
synchronized (this) { | |
removeCachedKey(lastAccessed); | |
bitmapKeyCache.put(updatedAccess, id); | |
} | |
} | |
/** | |
* Add a new last accessed key | |
* @param lastAccessed | |
* @param id | |
*/ | |
private void addKeyCache(Long lastAccessed, String id) { | |
synchronized (this) { | |
Logger.debug(this, "LFUMemoryCache:addKeyCache lastAccessed " + lastAccessed + " for id " + id); | |
bitmapKeyCache.put(lastAccessed, id); | |
} | |
} | |
/** | |
* Remove a cached key | |
* @param lastAccessed | |
*/ | |
private void removeCachedKey(Long lastAccessed) { | |
Logger.debug(this, "LFUMemoryCache:removeCachedKey lastAccessed " + lastAccessed); | |
synchronized (this) { | |
String remove = bitmapKeyCache.remove(lastAccessed); | |
if (remove == null) { | |
Logger.error("removeCachedKey: could not remove " + lastAccessed + " from bitmapKeyCache"); | |
} | |
} | |
} | |
@Override | |
protected Reference<Bitmap> createReference(Bitmap value) { | |
return new WeakReference<Bitmap>(value); | |
} | |
@Override | |
protected int getSize(Bitmap bitmap) { | |
return bitmap.getRowBytes() * bitmap.getHeight(); | |
} | |
@Override | |
protected Bitmap removeNext() { | |
Logger.debug(this, "LFUMemoryCache:removeNext"); | |
Bitmap nextBitmap = null; | |
Long lastAccessed = null; | |
String lastAccessedId = null; | |
CacheItem cachedItemTobeRemoved = null; | |
synchronized (this) { | |
if (bitmapKeyCache.size() == 0) { | |
Logger.debug(this, "LFUMemoryCache:clearMemory. bitmapKeyCache size is zero"); | |
return null; | |
} | |
lastAccessed = bitmapKeyCache.firstKey(); | |
lastAccessedId = bitmapKeyCache.get(lastAccessed); | |
cachedItemTobeRemoved = bitmapCache.get(lastAccessedId); | |
} | |
if (lastAccessedId != null && cachedItemTobeRemoved != null) { | |
Logger.debug(this, "LFUMemoryCache:removeNext. Found lastAccessedId " + lastAccessedId); | |
nextBitmap = cachedItemTobeRemoved.getBitmap(); | |
remove(lastAccessedId); | |
} else { | |
if (cachedItemTobeRemoved == null) { | |
Logger.error("cachedItemTobeRemoved is null of lastAccessedId= " + lastAccessedId); | |
} | |
Logger.error("Could not remove bitmap id of " + lastAccessedId); | |
removeCachedKey(lastAccessed); | |
} | |
return nextBitmap; | |
} | |
/** | |
* Set the ImageView associated with the bitmap | |
* @param loadedImage | |
* @param imageView | |
*/ | |
public void setImageView(Bitmap loadedImage, ImageView imageView) { | |
String key = getKey(loadedImage); | |
if (key == null) { | |
Logger.error("LFUMemoryCache:setImageView Could not find key"); | |
return; | |
} | |
CacheItem cacheItem = bitmapCache.get(key); | |
if (cacheItem != null) { | |
cacheItem.setImageView(imageView); | |
} else { | |
Logger.error("LFUMemoryCache:setImageView Could not find cache for key " + key); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In checkMemory method there is a variable called systemMaxMemory. What would the possible value for this one? there is no initialization or value setting in your code?