I'm new to android and am working on my first real app so i apologize if this question has been answered, or isn't detailed enough. I'm having the common OutOfMemoryError when loading bitmaps to my android app. I've searched around on stackoverflow and went through the google tutorial that describes how to use InSampleSize to reduce the memory for each bitmap.
When testing out the app to see how much memory I was saving by down scaling my bitmaps I noticed that the heap size was still growing at the same rate. I.E. When I use an InSampleSize = 8 vs not scaling the bitmap at all (or InSampleSize = 1) the heap grows the same for each.
I have tried printing the byteCount with:
myImage.getByteCount()
for both of the scaling SampleSizes listed above and they both have identical ByteCounts. Is this the expected behavior? Shouldn't the byte count be reduced since I'm downscaling images?
I was expecting to see the memory used to the bitmap reduced by a factor of 8 or so. Am I missing something, or is my understanding of image scaling incorrect? Thanks.
edit: After doing some testing I discovered that if I used createScaledBitmap the memory was reduced but it required me first inflating the original Bitmap then scaling. I'm assuming this is non-ideal since it requires that. I thought the first method would do this for me but according to my heap dumps it isn't.
Initial Code and heap dump:
private static Bitmap decodeSampledBitmap(Resources res, int resId, int width, int height
{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Config.ARGB_8888;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Updated code (same as above with the only difference being) and heap dump:
private static Bitmap decodeSampledBitmap(Resource res, int ResId, int width, int height)
{
....
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);
return Bitmap.createScaledBitmap(bitmap, options.outWidth/options.inSampleSize, options.outHeight/options.inSampleSize, false);
}
It won't let me post images due to my reputation but according to heap dump it is using 79.155MB. Using the second technique the heap dump is using 26.912 MB.
Are you scaling the Bitmap after decoding it using InSampleSize?
Related
I know I can use these ways to get the Bitmap's size:
bitmap.getAllocationByteCount(); //API 19
bitmap.getByteCount(); //API 12
bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
But This all need a Bitmap object which mean I need to decode the bitmap into memory before, this may case OOM Exception. So I use this way to get the size before I can get Bitmap object:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(bitmapPath, options);
int picWidth = options.outWidth;
int picHeight = options.outHeight;
int size = 4 * picHeight * picWidth; //Byte
I think it cost 32 bits per pixel because Android decode bitmap use RGB_8888 default.
Is this a correct way or is there a better way to do this ?
You can select the bitmap configuration with BitmapFactory.Options.inPreferredConfig. This will allow you to specify a configuration where you know for sure how many bytes per pixel will be occupied by the Bitmap. I believe RGB_8888 is the default.
You can probably not 100% reliably prevent an OOM on a bitmap decode since you don't have a guarantee for a set amount of contiguous free space in memory for the allocation of the Bitmap. But you can certainly adjust your sample size and config to reduce the load.
Just create a File object with the path to the Bitmap. Then get file.length() to get the file size.
e.g. File bitmap = new File(pathToBitmap);
bitmap.length();
You'll have to put a try/catch clause as required.
I am creating a bitmap and it takes about 11 mb in the heap , though it is of the small size. Well I wanted to know if I can create the bitmap and also sclae it as a same time. The reason I want to do it is , the memory allocation , as If I understand correctly from different bitmap questions which are posted here , and that is
The bitmap allocates the memory as when it is created
So if its , then scaling it again take some process time and also increase the heap size until and unless the garbage collection is not occurred
So what I am doing is
screenHeight = displaymetrics.heightPixels;
screenWidth = displaymetrics.widthPixels;
float aspectRatio = screenWidth / screenHeight;
int modifiedScreenHeight = 400;
int modifiedScreenWidth = (int) (modifiedScreenHeight * aspectRatio);
mBitmap = Bitmap.createBitmap(modifiedScreenWidth, modifiedScreenHeight, Bitmap.Config.ARGB_8888);
So now it is creating the bitmap and allocation the memory , by memory analyzer tool in android studio I can see that it took 11mb in memory.
But I want to minimize them ,I have visited a link and I want to do some more scaling by options as show in this video . but it uses the file to decode such as
BitmapFactory.decodeFile(??,options);
where as I have no file to decode from , I want to decode it from the bitmap I created and to wash away the last created bitmap to clear the memory.
Or if it is possible to set the options when creating it so that we can avoid from extra memory allocation .
Please help.
You can use this using BitmapFactory.Options - specifically, use the options to decode the width / height of the bitmap, then sampleSize to determine how large the generated bitmap will be.
According to your example, you'd like the width/height of the bitmap to be 400 by 400 * aspectRatio. So, first, you'll need to see how large the bitmap needs to be. Do this as so:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(??, options);
int bitmapWidth = options.outWidth;
int bitmapHeight = options.outHeight;
This action will only decode the bitmaps size, without actually allocating memory for the bitmap's pixels. This is good because it's a very quick and light operation which doesn't require much resources and helps you make a more educated decision when loading the bitmap. Now we must use these size to determine how big the generated bitmap will be.
int sampleSize = 1;
while (bitmapWidth / sampleSize > 400 && bitmapHieght / sampleSize > 400 * aspectRatio)
sampleSize *= 2;
sampleSize must be a power of 2 for this to work, and what it will do is determine how many pixels to "skip" when reading the bitmap into memory. This algorithm will set a sample size to a size equal to 1st sample size which will produce a bitmap immediately smaller than the required bounds. You can tweak this if you'd like a slightly different implementation.
Now that you have the sample size, set it with in the options object and load the actual bitmap:
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(??, options);
The generated bitmap will be smaller than the required bounds, thus limiting your memory requirements for creating the bitmap object.
Hope this helps.
Im determining the size that a bitmap will take in memory using the following method:
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, bitmapOptions);
long bitmapSizeInMB = (bitmapOptions.outWidth * bitmapOptions.outHeight * 4) / 1048576L);
bitmapOptions.inJustDecodeBounds = false;
And the result, for example, is 5MB
But what happens is, the moment I decode the bitmap using
bitmap = BitmapFactory.decodeFile(imagePath, bitmapOptions);
and set it to an imageview, the GC system message in Logcat says RAM usage has increased by 20 MB, not 5.
So my question is, by doing this bitmap size check operation, do I increase the RAM usage?
I think the ImageView allocates ram for the image based on the bitmaps size.Regardless if the bitmap has been set or not, since you supplied bitmapOptions which comes with the dimensions.
I have a question that I seem to find the answer nowhere.
Does this lines of code:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
actually mean that, that file is being downloaded? Android docs say something like this:
decode with inJustDecodeBounds=true to check dimensions
and
Does:
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
BitmapFactory.decodeStream(is, null, options);
means that it will actually download the file smaller (not downloaded as original size and copied after that to a smaller size bitmap).
Clear example: I have some url's that point to many 2000 x 1500 images. By decoding those files and loading them to bitmaps, do I need to have enough memory for downloading the file at its full resolution (2000 x 1500), if I only need thumbnails of (200 x 150)?
I know another answer has already been accepted as the right one but for clarity...
This line options.inJustDecodeBounds = true; means that the call to BitmapFactory.decodeStream(is, null, options); doesn't get the bitmap information but does get bounding information and the MimeType.
You can then use these returned values, outWidth, outHeight and outMimeType to get a 'resampled' version of the bitmap data by setting options.inJustDecodeBounds = false; and setting the sample size to a given ratio determined by your desired output dimensions options. inSampleSize = [int].
See this very informative page for more information: Loading Large Bitmaps Efficiently
Indeed looks like the file is being downloaded, and the effiency part comes where the bitmap being loaded in memory is the smaller one ( the one being decoded with inSampleSize options).
How to solve OOM issue in android . I have tried almost every things like scaling bitmap,inPurgeable in BitmapOption,releasing all resources etc. but still getting OOM issue.
This is basically in images taken by camera or any image i.e. larger then 1.5 mb. I have also images 15-20 mb size in my app.
this is what i´m doing to avoid OOM errors, Using some of the code of the android training. This is in my class "ScaledFactor"
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inPurgeable = true; // I aded this to the android training code, without this I still have OOM errors. so try using inPurgeable = true
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
In my Activity I use
background = ScaledFactor.decodeSampledBitmapFromResource(getResources(), R.drawable.menu, screenheight, screenwidth); // screeenhieght is the output height , screenwidth is the output width
Then in the on destroy method, or after calling an other intent I use background.recycle();
I´m not using the hdpi, ldpi and so folders... I just use the drawable with large bitmaps, and do the scalling. this way you save some mb on the final apk file
The android training code is here for more info http://developer.android.com/training/displaying-bitmaps/load-bitmap.html#load-bitmap
C ya ! Hope this helps, I spent a hole day trying to figure this out and reading all the questions and answers in this forum. This is just the example of the background image but i have more than 20 images in my game all loaded this way but with smaller output size, and it works very smooth.
Have you tried Bitmap.recycle(); ?
It once solved my Out of Memory issue.