5

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?

Sudarshan Bhat
  • 3,772
  • 2
  • 26
  • 53
  • 1
    [Have you searched anything before for this?](http://www.google.com/search?q=java.lang.OutOfMemoryError%3A+bitmap+size+exceeds+VM+budget+in+ListView+and+lazy+loading+images&ie=utf-8&oe=utf-8&aq=t) – Praveenkumar Aug 09 '12 at 13:12
  • Yes. I did and there are hundred similar threads. But most of the times the proposed answers are to force GC which is of no use as BitMap images are not stored in heap. – Sudarshan Bhat Aug 09 '12 at 13:13
  • How large are your bitmaps? If they're larger than the size you need it may be worth scaling them down with `BitmapFactory.Options`. – Alex Curran Aug 09 '12 at 13:16
  • Every image is about 100x100. Not bigger than that. Is that big? Specially when there are 700 or 800 such images in a single ListView? – Sudarshan Bhat Aug 09 '12 at 13:24
  • I'm already scaling images using BitmapFactory.Options :( Still no use. – Sudarshan Bhat Aug 09 '12 at 13:27
  • possible duplicate of [Android: Strange out of memory issue while loading an image to a Bitmap object](http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object) – Amir Afghani Aug 11 '12 at 21:31
  • I have faced this before it really sucks. Is this a 4.0 problem or below 4.0 secondly have your images in sdcard instead of fetching from the network. Don't keep it in a hash map with key as URL and value as image. It would eat down the whole heap and just putweak reference of bitmaps if you wish and load if they are null. Also set the image view as null if you can. Since I am on mobile there might be spelling mistakes. – Jayshil Dave Sep 19 '12 at 12:46

2 Answers2

1

Try the Universal Image Loader

It's an open-source project and they have blog posts about how to use it. I've already used it in several projects and I'm not having problem even in big long lists.

Enjoy!!

onkar
  • 4,427
  • 10
  • 52
  • 89
Neto Marin
  • 674
  • 3
  • 15
1

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.

Sudarshan Bhat
  • 3,772
  • 2
  • 26
  • 53