java.lang.OutOfMemoryError: bitmap size exceeds VM budget.
This is one phrase you'll come across frequently in android while you try to load several large bitmaps in your program.And Why?Because the heap size available to an application is limited and large bitmaps are the easiest way of exhausting it.
So how do you work around it?
Understand that mostly,it is pointless to load a bitmap in its full capacity onto a device's screen that fits on your palm.So,all you have to do is to load a scaled down version of the bitmap and everything will be well and good.But how do you scale down a bitmap for loading? Configure and use the BitmapFactory.Options class while decoding the bitmap .
Here,for simplicity,I'll be loading a bitmap on to a single ImageView.
Before we begin,take a look at BitmapFactory.Options.inJustDecodeBounds and BitmapFactory.Options.inSampleSize.
In simple words,here's what they mean:
1. BitmapFactory.Options.inJustDecodeBounds: When set to true,we will be able to measure the bitmap dimensions,without actually loading the bitmap into memory.
2.BitmapFactory.Options.inSampleSize: The value of this int allows us to load a scaled down version of the bitmap into memory. As an example,if the value set to this variable is 2, the resulting bitmap will have a width half the original width and height set to half the original height.
That is,
New width= original width/2
New Height= original height/2.
Here,we'll be dedicating a class for loading the bitmap and in the class here's what we are going to do:
1. Measure the actual height and width of the bitmap by setting inJustDecodeBounds=true;
2. Obtain an appropriate scale(which we will set to inSampleSize),using the actual height and width.
3. Load the scaled down bitmap using the inSampleSize.
Note that this article is an implementation of the guidelines given here.
As I have said,my main.xml contains only a single ImageView,for the purpose of demonstration.
Now for the code,read the comments and you will find it self explanatory.
Note that this article is an implementation of the guidelines given here.
As I have said,my main.xml contains only a single ImageView,for the purpose of demonstration.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ImageView android:id="@+id/bitmapView" android:layout_width="100dp" android:layout_height="100dp"/> </LinearLayout>
Now for the code,read the comments and you will find it self explanatory.
package com.blog.simplebitmaploader; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Environment; import android.widget.ImageView; public class BitmapLoaderActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //path of the file to be loaded String filePath=Environment.getExternalStorageDirectory()+ "/Sunset_Beach.jpg"; setContentView(R.layout.main); ImageView bitmapView=(ImageView) findViewById(R.id.bitmapView); //load the scaled down version bitmapView.setImageBitmap(BitmapLoader.loadBitmap(filePath, 100, 100)); } } class BitmapLoader { public static int getScale(int originalWidth,int originalHeight, final int requiredWidth,final int requiredHeight) { //a scale of 1 means the original dimensions //of the image are maintained int scale=1; //calculate scale only if the height or width of //the image exceeds the required value. if((originalWidth>requiredWidth) || (originalHeight>requiredHeight)) { //calculate scale with respect to //the smaller dimension if(originalWidth<originalHeight) scale=Math.round((float)originalWidth/requiredWidth); else scale=Math.round((float)originalHeight/requiredHeight); } return scale; } public static BitmapFactory.Options getOptions(String filePath, int requiredWidth,int requiredHeight) { BitmapFactory.Options options=new BitmapFactory.Options(); //setting inJustDecodeBounds to true //ensures that we are able to measure //the dimensions of the image,without //actually allocating it memory options.inJustDecodeBounds=true; //decode the file for measurement BitmapFactory.decodeFile(filePath,options); //obtain the inSampleSize for loading a //scaled down version of the image. //options.outWidth and options.outHeight //are the measured dimensions of the //original image options.inSampleSize=getScale(options.outWidth, options.outHeight, requiredWidth, requiredHeight); //set inJustDecodeBounds to false again //so that we can now actually allocate the //bitmap some memory options.inJustDecodeBounds=false; return options; } public static Bitmap loadBitmap(String filePath, int requiredWidth,int requiredHeight){ BitmapFactory.Options options= getOptions(filePath, requiredWidth, requiredHeight); return BitmapFactory.decodeFile(filePath,options); } }
Simple,right?? The only disadvantage of this code is that everything related to the bitmap is being done in the UI thread,which could slow down your application.In my next post,we'll see how this code can be executed in a different thread.For now,adios..:):):).
The case you describe here allows you to work with smaller versions of the image(s) you need. But what about the case you need to load an image from camera folder (8 MP camera on galaxy), to process the image (let's say adding a border and a piece of text on it) and sending to a webservice. This is a scenario I have in one of my applications and I'm a bit stuck on this error.
ReplyDeleteI got Out of memory exception in the case above, and I have one more scenario: I need to rotate the image 15 degree and to build another image - bigger than the image I need to rotate).
How do you work in those cases?
hey zeltera..i'm sorry but I have'nt worked on any kind of image processing on android yet..
DeleteIn my case I encountered this error while loading images from various sources onto a ListView..I'l be glad if you have worked around your problem..
n yeah..sorry for the late reply..been out of touch with the world for months..;-)
Thanks, I was looking for a way to do this without loading the Bitmap into memory!
ReplyDeleteOne suggestion though. In the getScale function you should calculate both the width and height scales and use Math.max or Math.min, for cropping or filling a view. Your example will show weird behavior if you try to display a landscape image in portrait view and vice versa.
thanx for the suggestion dude..
Deletemy idea was to compute a scale using the the smaller dimension..i guess your suggestion fits too..:)
Thank you very much. Helped us to fix a long standing issue.
ReplyDeletePrasanth Kumar.S
If OOM was caused by loading an 8 mb image, this solution will work as I will essentially load scaled down bitmap, say, of 2 mb. But what if I load several images of 2 mb in size? OOM will still occur. How to resolve that??
ReplyDeleteI suppose you'll have to find a balance between your requirement and the memory available by tuning the bitmap options.
Delete