java.lang.OutOfMemoryError: bitmap size exceeds VM budget - Android

I developed an application that uses lots of images on Android.

The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.

There is no animation, no special effects or anything that can fill the memory. Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.

Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.

Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.

So what is the best/correct way to handle many images?

Should I put them in static methods so they are not loaded all the time? Do I have to clean the layout or the images used in the layout in a special way?


Solution 1:

One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.

Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.

First, set the “id” attribute on the parent view of your XML layout:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().

    @Override
    protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
    }

    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
        ((ViewGroup) view).removeAllViews();
        }
    }

This unbindDrawables() method explores the view tree recursively and:

  1. Removes callbacks on all the background drawables
  2. Removes children on every viewgroup

Solution 2:

It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.

It's difficult to say why this is without looking at your code. However, this article has some tips that might help:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.

Solution 3:

To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:

public final void setMyBitmap(Bitmap bitmap) {
  if (this.myBitmap != null) {
    this.myBitmap.recycle();
  }
  this.myBitmap = bitmap;
}

And next you can change myBitmap w/o calling System.gc() like:

setMyBitmap(null);    
setMyBitmap(anotherBitmap);