snapfractalpop
snapfractalpop

Reputation: 2134

How can I avoid java.lang.OutOfMemoryError: bitmap size exceeds VM budget in android while using a large bitmap?

I realize that there are many similar questions, but most involve scaling down the bitmap, or explicit calls to bitmap.recycle() and System.gc() which doesn't guarantee anything (and in my case failed to prevent the error).

Edit: I have also tried using isPurgable = true when creating my bitmap.

Edit: Also, I have only tested this with Froyo (2.2.2) on Motorola Droid.

Here is the scenario: I am loading one bitmap (width: 1500, height: 2400). This takes up roughly 14 MB. The rest of the app is minuscule with regard to memory consumption (easily less than 2 MB).

I am using the bitmap with a transformation matrix to pan and zoom around on a surface view. On first load, this works perfectly. However, when I exit the app and relaunch it, I get the dreaded OutOfMemoryError. On third launch it works, on fourth it crashes ... and so on.

I don't need to save state, and so I tried calling finish() in onPause() (as well as the recycle() and gc() methods mentioned above). Finish() seems to stop all threads, but does not clear the memory.

I should mention that I am also using a technique which I found in a comment from this question. Also Please check this

So, my image is loaded from the web, as an immutable bitmap. Its bytes are then saved to sdcard (very slow) just to be reloaded back to a mutable bitmap. If jumping through all these hoops is laughable, please educate me...

For my case, clearing all memory allocated for the app would be acceptable (if it doesn't generate crash messages). Is there anyway to just totally clear the memory allocated to my app so that each restart is as clean as the first launch?

Is there any solution involving tiling? Surely I am missing something.. since the image file itself (a png) is only a few kilobytes, and I have viewed larger images in the stock gallery app without this problem.

Edit: I have determined the cause of the problem based on insight gleaned from @Jason Lebrun's answer. It turns out that the canvas I had used to draw on this bitmap held a reference too it, so that canvas needed to be set to null for it to be properly garbage collected. Hope this helps someone with a similar issue.

Upvotes: 2

Views: 2423

Answers (6)

Flying Monkey
Flying Monkey

Reputation: 669

This method takes in the image path and gives you a drawable without crashing

For best possible quality of image,always call this method with argument imageSizeDivide's value =1

public Drawable recurseCompressAndGetImage(String image_path,
        int imageSizeDivide) {
    try {

        Log.w("", "imageSizeDivide = " + imageSizeDivide);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = imageSizeDivide;// controls the quality of image

        // Bitmap           
        Bitmap srcBmp = BitmapFactory
                .decodeFile(image_path.trim(), options);

       //next if-else block converts the image into a squire image.Remove this block if u wish
        if (srcBmp.getWidth() >= srcBmp.getHeight()) {

            dstBmp = Bitmap.createBitmap(srcBmp, srcBmp.getWidth() / 2
                    - srcBmp.getHeight() / 2, 0, srcBmp.getHeight(),
                    srcBmp.getHeight());

        } else {

            dstBmp = Bitmap.createBitmap(srcBmp, 0, srcBmp.getHeight() / 2
                    - srcBmp.getWidth() / 2, srcBmp.getWidth(),
                    srcBmp.getWidth());
        }

        dstBmp = Bitmap.createScaledBitmap(dstBmp, 400, 400, true);

        return new BitmapDrawable(mContext.getResources(), dstBmp);

    } catch (OutOfMemoryError e) {

        //reduce quality and try again
        return recurseCompressAndGetImage(image_path, imageSizeDivide * 2);

    }

}

Upvotes: 0

Raykud
Raykud

Reputation: 2484

well whenever you are saving the image to SD card you can use a lower quality of the image like:

myBitmap.compress(Bitmap.CompressFormat.PNG, 85, fileOutputStream);

and/or also use the bitmapFactory options to get a smaller image to save memory:

BitmapFactory.Options factoryOptions= new BitmapFactory.Options();
factoryOptions.inSampleSize = samplesize;

note that calling recicle() and gc() doesn't mean that the resources will be freed immediatly. As the docs say:

from myBitmap.recycle():

Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

and from System.gc():

Indicates to the VM that it would be a good time to run the garbage collector. Note that this is a hint only. There is no guarantee that the garbage collector will actually be run.

therefore your app might be still using the resources and then whenever you re open the app the system is still using the resources plus the new ones that you are generating, and that might be the reason of why you are getting the out of memory error. So in short handling a large image in memory is not a good idea.

Upvotes: 1

Jason LeBrun
Jason LeBrun

Reputation: 13293

Are you experiencing this problem on Gingberbread, or a different version? Gingerbread has a lot of problems with apps that use a lot of Bitmap memory, so knowing the OS version can help with determining the nature of the problem.

In your case, it's hard to say exactly what might be the cause. However, with a 14MB bitmap, even a 2nd instance is likely to use up your available heap and cause a crash. On Gingerbread, it's pretty easy to end up with Bitmap memory sticking around for longer than it should, due to the concurrent GC + the fact that Bitmap memory is allocated in a native array.

When you exit the app, it's probably not being unloaded from memory. That would explain your crash pattern: the first time you run the app, the large bitmap is loaded. The 2nd time you launch it, it's actually just restarting an Activity for a process already in memory, and for some reason, the memory for the Bitmap is still hanging around taking up room. So the app crashes, and a new process is started, with a fresh heap. If recycling is not helping, you might still have a reference to the previous Bitmap sticking around. If you're always using the same Bitmap, reusing a static reference to it might help, although I'm not sure.

Are you sure that the Bitmap is not leaked via a leaked context, long running background code, or something similar?

Other answers here have good advice, which is to try tiling the Bitmap after you fetch it, and only loading tiles as necessary. If you don't need to support older versions, you can use BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) to do this, but only on devices that support API level 10 or higher.

Upvotes: 4

mmaitlen
mmaitlen

Reputation: 822

You can use something like the following to decrease the sample size. I use this method in one of my apps to display images from the assets directory. But you can play around with the sample size, I've used values of 1 for images that aren't to big (94kb) and 4 for larger images (1.9mb)

protected void loadImageIntoBitmap( String imageAssetFile, Bitmap bitmap, int sampleSize, ImageView imgvw ) throws IOException {
    InputStream is = getContext().getAssets().open( imageAssetFile );
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeStream( is,null,options );
    imgvw.setImageBitmap( bitmap );

    is.close();
}

Upvotes: 2

Jon O
Jon O

Reputation: 6591

To expand on @jtietema's answer, have you tried loading/rendering only the part of your bitmap that would be visible after applying your transformation matrix? You could do this by using a bounds-only bitmap for the whole image and transforming that, and using the resulting rectangle as an input to your acquiring-bitmap-from-sd-card.

Upvotes: 2

Jeroen
Jeroen

Reputation: 3429

What are you doing with the bitmap? The resolution is way higher than that of your android device, so if you are viewing the whole bitmap than scaling it down would do.

If you are zooming in you could create just a subset of the bitmap and just load the part that is visible.

Upvotes: 1

Related Questions