Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.lang.OutOfMemoryError: bitmap size exceeds VM budget in ListView and lazy loading images

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?

like image 243
Sudarshan Bhat Avatar asked Nov 04 '22 18:11

Sudarshan Bhat


1 Answers

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.

like image 135
Sudarshan Bhat Avatar answered Nov 15 '22 01:11

Sudarshan Bhat