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?

7
  • 1
    Have you searched anything before for this? Commented Aug 9, 2012 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. Commented Aug 9, 2012 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. Commented Aug 9, 2012 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? Commented Aug 9, 2012 at 13:24
  • I'm already scaling images using BitmapFactory.Options :( Still no use. Commented Aug 9, 2012 at 13:27

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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!!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.