Skip to content

Instantly share code, notes, and snippets.

@kevindmoore
Created December 7, 2012 16:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevindmoore/4234603 to your computer and use it in GitHub Desktop.
Save kevindmoore/4234603 to your computer and use it in GitHub Desktop.
Universal ImageLoader Recycling Memory classes
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;
}
}
/**
* 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;
}
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();
/**
* 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));
}
}
/**
* 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();
}
}
}
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);
}
}
}
@ccfiel
Copy link

ccfiel commented Mar 9, 2013

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment