4

I've looked all over for "Bitmap size exceeds VM budget" problems, but none of the solutions seem applicable for me. I am not understanding why my program sometimes throws this error because they way I'm using it doesn't seem to cause any possible memory leaks. My stack traces are pointing to the BitmapFactory.decodeResource() method. I've got a background image that I'm using to draw on a Canvas and this is how I've been initializing it:

Bitmap backgroundImage = BitmapFactory.decodeResource(getResources(),
    R.drawable.background);
backgroundImage = resizeImage(backgroundImage, w, h);

This is how I've been using it:

canvas.drawBitmap(backgroundImage, 0, 0, paint);

I thought that putting backgroundImage = null in the onDestroy method would help, but that did nothing. There is not other reference to the background image resource in my program except in an XML file, but I don't think that affects it. Could someone explain to me why this is happening and how to fix it?

By the way, there is not screen orientation changes involved in this app.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Brian
  • 7,955
  • 16
  • 66
  • 107
  • Do you have both a field and a local variable named `backgroundImage`? You can easily check for this in Eclipse by going to Window > Preferences, then navigating to Java > Compiler > Errors/Warnings and setting "Local variable declaration hides another field or variable" to Warning. – Ted Hopp Jun 13 '11 at 23:56
  • No there's only one backgroundImage in the class and it's a private class variable. – Brian Jun 13 '11 at 23:57
  • 1
    It's just that this code: `Bitmap backgroundImage = BitmapFactory....` looks like a local variable declaration. – Ted Hopp Jun 13 '11 at 23:59
  • How often are you calling this code? It's possible you're running out of memory from loading the same image into memory over and over and the GC can't keep up. If you call it frequent enough and the image never changes.. consider making backgroundImage a field. – dymmeh Jun 14 '11 at 00:01
  • I'm sorry, I made it look like a local variable just to show the type; but in the real program it's a class variable. I don't think this would affect it, would it? – Brian Jun 14 '11 at 00:01
  • @Aero - check the answer to [this thread](http://stackoverflow.com/questions/1586685/outofmemoryerror-bitmap-size-exceeds-vm-budget-android). – Ted Hopp Jun 14 '11 at 00:03
  • Actually, let me elaborate. This error isn't coming up all of sudden. It only appears during the onCreate when the activity is started. That's why I thought that null'ing the object during onDestory would remove references so when the activity started up again later on, it would encounter this. – Brian Jun 14 '11 at 00:03
  • That error is because you have run out of space in the native heap - see http://stackoverflow.com/questions/1955410/bitmapfactory-oom-driving-me-nuts/5493182#5493182 for more details. How big is the bitmap - width x height x bits-per-pel? Does this happen the first time through or on later runs? What else is your app / the system doing when you hit the error? – Torid Jun 14 '11 at 00:07
  • Well it's fairly difficult to reproduce this problem because it happens on a random basis. But it generally happens on a restart of this activity, not it's first time. – Brian Jun 14 '11 at 00:10
  • I think I'm just going to count on System.gc() to take care of the problem even though the Android documentation says not to. Thanks for your help! – Brian Jun 14 '11 at 00:15

1 Answers1

3

You need to free the bitmap pixels when you're done with it. You mentioned that you set its value to null, but that only makes it eligible for GC, it does not explicitly tell the VM that you're done with those pixels, and that now is a good time to free them.

Before you set it to null, simply call Bitmap#recycle() on the Bitmap:

protected void onDestroy() {
    if (this.backgroundImage != null) {
        this.backgroundImage.recycle();
        this.backgroundImage = null;
    }
}

Additionally, you may be wasting resources in your resizeImage() method, which you did not provide code for. It's much more efficient to do proper down-sampling of the Bitmap at decode-time, rather than loading the full-size Bitmap, and then scaling it down from there.

The general technique is to use the 3-argument version of BitmapFactory.decodeResource(), with BitmapFactory.Options#inJustDecodeBounds for a first-time-pass in order to get the width/height of the Bitmap (although in your case, since it comes from the app's resources, there's no reason you should even have to do that.. but I'll explain it anyway); then determine a proper samplesize based on the target size, and decode the bitmap a second time. That usually results in much less memory usage, especially for very large images (e.g., with inSampleSize set to 2, it decodes the full-size Bitmap, but only allocates enough memory for a Bitmap of half the original size, downscaling the Bitmap in the process).

Community
  • 1
  • 1
Joe
  • 42,036
  • 13
  • 45
  • 61
  • See https://groups.google.com/group/android-developers/browse_thread/thread/bd858a63563a6d4a for another explanation and example code. Also, another thing to look out for: make sure you're only decoding the Bitmap **once** in a single instance of the Activity (e.g., do it in `Activity#onCreate()`, not in some other method that gets called more than once) – Joe Jun 14 '11 at 02:37
  • I've looked at a couple of examples, but I don't get what I am suppose to do. So what code would I write to decode the bitmap resource and take it in with dimensions, say, 480x800? – Brian Jun 14 '11 at 20:12
  • Good question: take a look at some code in the Gallery app, which computes an appropriate sample size to use for "inSampleSize": http://android.git.kernel.org/?p=platform/packages/apps/Camera.git;a=blob;f=src/com/android/camera/Util.java;h=844a75dcf074be95cf6f0f5a099ead7fc8c29c5d;hb=refs/heads/master Look at computeInitialSampleSize() and computeSampleSize(). You would call it with something like: `Util.computeSampleSize(options, 480, 480*800);` That means "smallest side should be at least 480px" and "scale it down until it fits inside 480x800". Do that between 1st and 2nd decode passes. – Joe Jun 14 '11 at 23:04
  • What about image backgrounds loaded in xml resources? – Maxrunner Apr 17 '12 at 14:31