I have a listview that probably has infinite items loaded on scrolling infinitely.
Each item in list view has one or two images which I'm lazy loading.
Everything works great but when I scroll for really long it crashes with this in log cat
08-07 15:26:25.231: E/AndroidRuntime(30979): FATAL EXCEPTION: Thread-60
08-07 15:26:25.231: E/AndroidRuntime(30979): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
08-07 15:26:25.231: E/AndroidRuntime(30979): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
08-07 15:26:25.231: E/AndroidRuntime(30979): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:493)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.decodeFile(LazyImageLoader.java:171)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.getBitmap(LazyImageLoader.java:112)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.access$2(LazyImageLoader.java:106)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader$ImageLoader.run(LazyImageLoader.java:197)
In my lazy image loader I am storing bitmaps in a WeakHashMap
. So garbage collector should collect the bitmaps right?
My lazy imageloader works something like this.
I call displayImage()
from my adapter with url and a reference to imageview
public void displayImage(String url, ImageView imageView, int defaultImageResourceId){
latestImageMetaData.put(imageView, url);
if(weakhashmapcache.containsKey(url)){
imageView.setImageBitmap(weakhashmapcache.get(url));
}
else{
enqueueImage(url, imageView, defaultImageResourceId);
imageView.setImageResource(defaultImageResourceId);
}
}
So if I find the image in cache, I set it directly, otherwise I queue it with function enqueueImage()
.
private void enqueueImage(String url, ImageView imageView, int defaultImageResourceId){ Image image = new Image(url, imageView, defaultImageResourceId); downloadqueue.add(image); // downloadQueue is a blocking queue which waits for images to be added //If the queue is about to get full then delete the elements that are ahead in the queue as they are anyway not visible Iterator iterator = downloadQueue.iterator(); while(iterator.hasNext() && downloadQueue.remainingCapacity() < 80){ downloadQueue.remove(iterator.next()); } }
And my image loader thread is this -
class ImageLoader extends Thread {
public void run() {
Image firstImageInQueue;
try {
while((firstImageInQueue = downloadQueue.take()) != SHUTDOWN_TOKEN)
{
Bitmap imageBitmap = getBitmap(firstImageInQueue.url);
if(imageBitmap != null){
weakhashmap.put(firstImageInQueue.url, imageBitmap);
BitmapDisplayer displayer = new BitmapDisplayer(imageBitmap, firstImageInQueue.imageView, firstImageInQueue.url, firstImageInQueue.defaultImageResourceId);
Activity activity = (Activity)firstImageInQueue.imageView.getContext();
activity.runOnUiThread(displayer);
}
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
imageLoaderTerminated = true;
}
}
}
getBitmap()
just fetches image from url scales and decodes it into a Bitmap object. BitmapDisplayer
is just a Runnable which does the setting of image to imageview on UI thread.
What am I doing wrong?
It has been a nightmare and after days & nights of research here are few points that may be useful to others.
Don't store all the Bitmaps in cache. Keep it swapping between Disk cache and Memory Cache. Number of bitmaps you store can depend on the heap limit that you get by calling
int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
.getMemoryClass();
I used a LruCache
instead of WeakHashMap
cache. LruCache
is available in the support package. It is very easy to replace your existing WeakHashMap
implementation with LruCache
. Android also has a beautiful documentation on Caching Bitmaps.
Jake Wharton's DiskLruCache
is a great way to manage Disk Cache.
Don't download huge bitmaps if you do not need it. Try to get a size that's just good enough to fit your need.
Using BitmapFactory.Options you can make some trade offs with image quality to hold more images in memory cache.
If you think there's anything more we could do, please add.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With