Skip to content

Solution for OutOfMemoryError: bitmap size exceeds VM budget

Saturday, 24 September, 2011

Some days ago I was facing this OutOfMemoryError error when developing an Android application. Then I Google’d it and found the reason that causes this type of error: I am using too much images and they are not released, being kept in memory and eventually the memory gets full.

According to Android Developers your device has at least 16MB of heap space (T-Mobile G1) for storing your application data. But as Yekmer said in  Yekmer’s Posterous , images are not stored in the heap space. The space reserved for images in an Android application is very small, and having a big application using a lot of images, may easily lead to the OutOfMemoryError.

So lets see an example.

Imagine that I want an application that do something. For doing “something”, the user needs to pass through a series of steps, lets say 10. So I create 10 activities, one leading to another. But because my application is very awesome, I want each activity to have a different background image, and some other image in it. Both images have decent resolutions (because I want to have a minimum image quality for big resolution devices). Also there is a button on each, that will move to the next activity. Both background and the image are configured in the layout xml of the activity.

Everything seems nice and simple. But when you run it, you may reach activity 8 or 9, never reaching the 10th Activity, because you may face the OutOfMemoryError. So what can I do? I really need those 10 steps, and I want the images to be stored, because I want to be able to press “back” and return to my previous activity!

This problem gave me a lot of headaches, and I had to search a lot for a decent solution. The solution is actually very simple: If I am storing a lot of images, and each Activity has 2 images, and only 1 activity is shown at a time, I could only store 2 images in 2 global variables, and all Activities would reuse those 2 images. Whenever it’s needed to go to the next Activity, that Activity would access to the global variables, changing its value, and then use them.

The only thing I need to do is:

Store the two images at the Application class (So they can accessed from all the Activities)

public class MyApp extends Application {
   private ImageView img; // the image
   private RelativeLayout bgimg; // layout of the activity
   private Bitmap nav; // the image in the Bitmap format
   private Bitmap background; // background in the Bitmap format
   private BitmapDrawable bg; // background in the Drawable format
}

I need also to create two methods at the Application class for each type of image: One that free the images, and other to allocate the new ones, replacing the previous one.

So for the background we have

public void loadBackground(int id) {
   background = BitmapFactory.decodeStream(getResources().openRawResource(id));
   bg = new BitmapDrawable(background);
   bgimg.setBackgroundDrawable(bg);
}
public void unloadBackground() {
   if (bgimg != null)
   bgimg.setBackgroundDrawable(null);
   if (bg!= null) {
      background.recycle();
   }
   bg = null;
}

and for the second image we have

public void loadBitmap(int id) {
   nav = BitmapFactory.decodeStream(getResources().openRawResource(id));
   img.setImageBitmap(nav);
}
public void unloadBitmap() {
   if (img != null)
   img.setImageBitmap(null);
   if (nav!= null) {
      nav.recycle();
   }
   nav = null;
}

I also created this two methods in the Application class to simplify the code at the Activities:

public void setBackground(RelativeLayout i, int sourceid) {
   unloadBackground();
   bgimg = i;
   loadBackground(sourceid);
}

public void setImage(ImageView i, int sourceid) {
   unloadBitmap();
   img = i;
   loadBitmap(sourceid);
}

Now we are going to call this methods in each Activity. Since we want to release the images from the last Activity to save space, we will call this methods at the onCreate() method, and because we may press the back button to go back to the previous activity, we want to call this methods at the onResume() methods, sou you will need to override it.

public class Activity1 extends Activity {
   private ImageView img; // The second image
   private MyApp app;
   private int navid = R.id.imageView1; // id of the imageView
   private int navdid = R.drawable.secondimage; // id of the image drawable
   private int bgid = R.drawable.bg1; // id of the background drawable
   private int layoutid = R.id.layout1; // id of the activity layout
   private RelativeLayout layout; // the layout of the activity

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.l1);
      app = (MyApp)getApplication();
      Button b = (Button) findViewById(R.id.button1);
      img = (ImageView)findViewById(navid);
      app.setImage(img, navdid); // free last image, and store new one
      layout = (RelativeLayout) findViewById(layoutid);
      app.setBackground(layout, bgid); // free last background, and store new one
      ...
   }
   @Override
   protected void onResume() {
      super.onResume();
      img = (ImageView)findViewById(navid);
      app.setImage(img, navdid);
      layout = (RelativeLayout) findViewById(layoutid);
      app.setBackground(layout, bgid);
   }
}

And that’s all. With this implemented in each Activity, you will have at most, 2 images loaded in your application, when in theory you would need to have 20!

The disadvantages of using this implementation are that the application may get a bit slower when going from one Activity to another, and when it goes, you may see the images disappearing before the screen changes to the new one. One solution for this is that instead of storing just two images, you buffer 3 pairs: the current pair for the present Activity, and the other two pairs for the previous Activity and the following Activity. It will be required a little bit more space, but you wouldn’t experience the images disappearing when changing between Activities.

I hope this post had helped you!

About these ads

From → Uncategorized

23 Comments
  1. This is a major issue for us and I wish there was better core support at the OS level for this scenario. Thank you for posting this code sample!!

  2. Jason permalink

    I have a better solution… make an iOS app instead. Sorry, I’m just appalled by the amount of extra code and workaround needed to do a simple task. I’m flipping through images that are no bigger than the resolutions I support. I’m removing ImageViews from the stack and doing everything I can to make this app perform like it’s iOS counterpart. It seems like the solution is to use terrible quality images. Yippee. No matter what hardware you throw at Android, it will limited by this arbitrary and restrictive VM budget. Stupid.

    • Thanks for your opinion :)
      But as a mobile developer, I understand that mobile application are much more limited in resources. Don’t forget that Android isn’t targeted for a specific device like iOS. It should work in all Android devices, from the worse to the best. This is an issue that should be solved by the OS, but it’s not the end of the world.

      I’m not an iOS developer (yet), I don’t know anything, but don’t you have to free the images in iOS too?

    • I agree with Jason. I’d like to consider myself a 15 year java coder. I picked up iOS in a weekend, and it was easy to deal with. This android memory management story is amateur hour and Google should be ashamed and I’m frustrated with this memory leak issue. Application vesrus Activity, versus use WeakREference. When I tried the Google recommendation to “unbindDrawables” I get an exception about Adapter view not having supported method.

      Par for the Android Course. Google shows some recommendation with some half bake code example, and trying ti blows up.

      Thanks for the solution here. Looks promising. Will give it a try

      • Emphasis on “Android memory management”. The only iOS memory management to speak of is built-in. Built into the developer, that is.

        I’ve programmed for both OSes and I’ll take Java garbage-collection over Objective-C reference-counting any day. If memory leaks are a problem with Android, they are an absolute nightmare with iOS.

  3. Hello, I would like to know if my ImageView’s picture was defined in layout xml…
    Did I need to set its background to null before finish activity ?

    • RR, I’m not sure about that, I was having problems when I defined the background in the XML, that’s why I tried the implementation explained in this article.

      If your solution works, feel free to say here :)

  4. thorstenmz permalink

    I get an
    java.lang.ClassCastException: android.app.Application
    in line
    app = (MyApp)getApplication(); :-(

    • Did you create the MyApp class correctly?
      Remember that MyApp extends your Application class, and you have to change your AndroidManifest.xml also..

      • Adeel Shahzad permalink

        Hello, sir.
        Sorry I am a bit newbie at Android dev. I know if a class extends Activity, you can change your AndroidManifest by using the tag. But what tag should be used if your class extends Application?

      • joaocruz04 permalink

        If you extend the Application class, you need to add in the manifest the “name” tag in the like:
        <application name="yourclass"

  5. pradeep permalink

    very good post, but what if a single activity contains multiple images, i have used unbinddrawables() method, i can’t able to judge the way it is working, sometimes it is managing very well but sometimes heap size was getting increased. Is there any solution for this.

    • One activity having multiple images is not a problem, unless the images are too big. Try to resize the images to a more suitable size, so it consumes less memory ;)

  6. kishore permalink

    I am getting black screen when navigating between activities. How can I resolve this. Thanks.

    • That’s because before the activity changes, the images are cleared from memory. To avoid this, you should have a stack of images like I said in the post. This way, you may have to store images of 2 or 3 activities.

      Here’s the part I wrote about that:

      “The disadvantages of using this implementation are that the application may get a bit slower when going from one Activity to another, and when it goes, you may see the images disappearing before the screen changes to the new one. One solution for this is that instead of storing just two images, you buffer 3 pairs: the current pair for the present Activity, and the other two pairs for the previous Activity and the following Activity. It will be required a little bit more space, but you wouldn’t experience the images disappearing when changing between Activities.”
      :)

  7. this is not the correct approach to solve this problem. Android has a proper activity architecture where each activity on the background can be completely destroyed and recreated when needed. I think your problem is that you are leaking your previous activities. proper approach is not to leak previous activities.

    to easily find leaked activities, use your app for a while, open DDMS, force-run garbage collector, get a memory dump, open it with Eclipse MAT and run the following OQL: “select * from instanceof “android.app.Activity”

    for each activity you see on that list, right click and click “find references to root excluding everything”. there you find what leaks that activity, fix it and you are good to go.

    • AlCruzzi permalink

      Thank you for the explanation. The thing is I tried create like 10 activities one leading to each other, each one with 1 image and 1 button, no more, no less. Each image loaded the usual way using the drawable resources. But then I had the Bitmap problem. Unless you free them, the system will keep references to them, and keeping them in memory. What you explained may be a very good way to find leaks, but it doesn’t solve the problem I mentioned. Try it with a 16-20 BItmap memory and create 10 activities like I explained, each one with a background image. You will see what I meant :)

  8. HuXTUS permalink

    Hello! Your solution is interesting, nay is one that can safe my app from crash, thank you.

    But I have problem: when my activities changes, i see some white flash, becouses background is removed and again applied.

    I tried do this:
    ———-
    First, all my activities is childrens on MyActivity class.
    ———–
    class MyActivity overrides setContentView method like this:

    public void setContentView(int layoutRedID) {
    LayoutInflater inflater = getLayoutInflater();
    LinearLayout view = (LinearLayout) inflater.inflate(layoutRedID, (ViewGroup) findViewById(R.id.layoutRoot));
    app.setBackground(view, R.drawable.background); // free last background, and store new one

    super.setContentView(view);
    }
    ————
    in loadBackground method I do:

    bgimg.refreshDrawableState();
    ———–

    But it not solved problem.

    PS: sorry for my english.

    • joaocruz04 permalink

      Hello HuXTUS,

      You can try have at least 2 Activities at the same time.

      Let’s say you move from activity A to B. What you can do is load activity B with startActivity, then when B is visible, you may finish/release drawables at activity A (You may use a flag and override the onPause method.

      And when you are going from B to A, do the same. Load A first, and when it’s loaded, then finish B.

      Today, what I do is starting the activities and if needed, finish the previous one, by calling startActivity followed by finish().If I need to go back, I override the onBackPressed and load the activity again, the same order (startActivity -> finish).

      Tell me if you solved your problem :)

  9. Thanks for you help. I realized that since an ImageView holds a reference to its image, using a WeakHashMap would be great for locating images that are already loaded and referenced somewhere else. My solution is here (open source): https://github.com/epabst/scrud/blob/7afcd76b7044e7d33db5c65c7575562db075dbdd/scrud-android/src/main/scala/com/github/scrud/android/util/ImageViewLoader.scala

  10. aMNA permalink

    M looking for that type of solution from many dAYS….THANXXXX ALOT…:)

Trackbacks & Pingbacks

  1. OutOfMemoryError when loading activities | PHP Developer Resource

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: