Nov 14, 2011

OutOfMemory exception when decoding with BitmapFactory



Google Android, Memory, and Bitmaps

Working on mobile devices forces one to make conscious decisions regarding coding choices, if for no other reason that resources are scarce (memory, screen size, bandwidth). Taking the easy route and ignoring wise mobile programming practices can take what could be a promising application and make it a disappointing user experience.

If you’ve spent any time with the Google Android SDK, and have tried to read a JPEG into a Bitmap using Media.getBitmap, you’ve almost certainly run into this little gem of an error message:

bitmap size exceeds VM budget

Unfortunately, since Android caps all applications’ VMs at 16MB in size, it only takes one or two big image reads to get you into trouble, regardless of all the garbage collection and Bitmap recycles you may try (see code snippet at the end of this post for more on that).

So, what’s a programmer to do?

Well, the only thing one can do is to read only what you need into memory. That means that you won’t be able to read in that 10MB jpeg sitting on your phone (at least, you won’t be able to read it reliably and / or repeatably) without trimming it down a bit.

The code below will do this for you; rather than calling Media.getBitmap, use this readBitmap function instead:





// Read bitmap
public Bitmap readBitmap(Uri selectedImage) {
Bitmap bm = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 5;
AssetFileDescriptor fileDescriptor =null;
try {
fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,”r”);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
finally{
try {
bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
fileDescriptor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bm;
}


The magic fairy dust in this function that allows you to trim down large bitmaps into digestible sizes is the options.inSampleSize property. inSampleSize – in this instance – takes a bitmap and reduces its height and width to 20% (1/5) of its original size. The larger the value of inSampleSize N (where N=5 in our example), the more the bitmap is reduced in size.

There’s also another coding practice that one should always use when dealing with Bitmaps in Android, and that is the use of the Bitmap recycle() method. The recycle() method frees up the memory associated with a bitmap’s pixels, and marks the bitmap as “dead”, meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing.

In my projects, I have a helper function that does cleanup when I am finished using a Bitmap, named clearBitmap:

// Clear bitmap

public static void clearBitmap(Bitmap bm) {

bm.recycle();

System.gc();

}



Dealing with memory errors on Android apps seems to be the “problem child” issue that crops up the most (like too many threads on Blackberry apps – different post for a different day).

But like parenting a problem child, what’s called for is attention and intention to mitigate havoc wreaked.

4 comments: