In my application, i have 3 screens containing multiple high resolution images. The number of images used in a screen is around 70-75. I have written the code to add images in a grid layout using an adapter class extending BaseAdapter, in the getView() method i wrote the code,
adapter = new ImageAdapter(this);
gridview.setAdapter(adapter);
int x = (int)(width/5.1f);
imageView.setId(position);
imageView.setLayoutParams(new GridView.LayoutParams(x,x));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(4, 20, 4, 20);
but while loading this screen, it show lots of memory issues, and in logcat i am getting the error,
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
Please share how could i write the code to handle memory issues with multiple high resolution images. Thanks.
If there is no way to adjust the images resolutions you should open them as BitmapFactory.Options().inJustDecodeBounds = true, pass your options to the images (desired adjusted size) and then decode them again using BitmapFactory.Options().inJustDecodeBounds = false.
The actual byte size of a bitmap image is calculated by multiplying the number of pixels by then number of bytes allocated for the pixel. ARGB_8888 (which is recommended) allocates 4 bytes per pixel, therefore, the size will be width * height * 4 Bytes.
For more details read the Loading Large Bitmaps Efficiently lesson from Android. Also this post should help.
Related
My app is using way too much memory, and I'm not too sure why. I'm making a solitaire game. I'm using a chained ConstraintLayout of 10 equal width RelativeLayouts (1 for each stack of cards). [I know I can use other Viewgroups].
My app is using ~15mb memory, until I do this;
public void DrawCardsMat0()
{
RelativeLayout mat = (RelativeLayout) findViewById(R.id.PLAY_Mat0);
mat.removeAllViews();
RelativeLayout rl = new RelativeLayout(this);
for (PlayingCard playingCard : Stack0.Cards)
{
ImageView myImg = GetCardImage(playingCard);
RelativeLayout.LayoutParams lay = new RelativeLayout.LayoutParams(CardWidth, CardHeight);
lay.setMargins(0, playingCard.UI_MarginTop, 0, 0);
mat.addView(myImg, lay);
}
}
and
public ImageView GetCardImage(PlayingCard playingCard)
{
ImageView myImg = new ImageView(this);
// a simple switch is used to here to pick the drawable. Removed as there's 52 cards)
myImg.setImageResource(R.drawable.s1);
return myImg;
}
CardWidth and CardHeight are ints (150 and 210, roughly. Pixels).
This draws FIVE cards, and each .jpg is ~60KB (yes, KB). This results in 60MB of memory usage. 10 cols == 600mb of memory (I assume, as it's OOM before then).
The images are in a RelativeLayout, and are placed on top of each other. MarginTop is used to "stack" them.
thanks in advance
--- update 1 ---
I'm now using a 46KB bmp image.
Creating 5 ImageViews (one stack's worth) in XML (and don't call anything in code) uses ~50mb memory. Tested using a LinearLayout.
Creating same images in-code uses same amount of memory.
--- update 2 ---
If I create a new Activity, with just a simple LinearLayout and 5 ImageViews, and no java code, it still uses ~50MB memory.
When working with bitmaps, you should always be careful. I'll describe the most important points and considering them, you can change the way work with card images is organized.
Bitmap in memory always exists in uncompressed format, so its size will be different from the size of your .jpg/.png/.webp/.[whatever compressed format]. The actual size depends on the Bitmap.Config, but by default Android will use ARGB_8888 (1 byte for alpha channel, 1 byte for each of R/G/B channels). You can roughly estimate the size in memory by saving the image in .bmp format.
When you use <bitmap> tag with drawable, it means in runtime bitmap will be wrapped in BitmapDrawable. As you can see, by default bitmap will be loaded as-is, taking original size.
When you call myImg.setImageResource(R.drawable.s1) it means that bitmap will have the same size as before in memory, it will be just scaled by using the matrix depending on the size of ImageView and scaleType you specified.
Considering that, probably you may want to load downscaled bitmap to the memory, depending on the view size. You can do that by using BitmapFactory class instead. I suggest you to read these 2 articles in order to understand how Android is working with bitmaps: Managing Bitmap Memory and Loading Large Bitmaps Efficiently.
You may also consider using some image library, like Picasso or Glide which can load downscaled bitmap depending on the size of ImageView. You can also try to use Fresco which keeps bitmap outside of JVM heap.
If you have such possibility, better to have cards in SVG format. Then you can work with vectors instead of having bitmap, which is much more memory-friendly (because SVG is just a set of instructions on how to draw). However in case of really complex SVG file you can hit another problem - it will take a lot of time to draw it. Check this article on how to use it with ImageView.
AFAIK, the inSampleSize attribute of BitmapFactory.options will read a sampled image as per the inSampleSize value. Eg: If value is 4, it will effectively read 1/16 pixels and thus memory required to load it will drastically reduce.
Here in fact, it is maintaining the aspect ratio in the sense that it has skipped 1/4th pixels along height and 1/4th pixels along the width.
When I load this bitmap in a smaller ImageView, aspect ratio is maintained and it looks good. I have used the following formula to derive the inSampleSize = max(Width/reqWidth, height/reqHeight)
size of the imageview = 100dp * 100dp, I have converted 100dp to pixels as per the screen density and used that result as the reqWidth and reqHeight.
(Note: All my images are bigger than the reqWidth and reqHeight)
However If I apply another operation Bitmap.createScaledBitmap() on above reduced version of bitmap, the image gets stretched and does not look good in the View.
I am not able to understand what createScaledBitmap() exactly does?
Given a Bitmap, let's call it bmp1, the create Scaled bitmap method creates a new Bitmap from bmp1 which is upscaled/downscaled to a new size.
However, since you're doing the scaling yourself, perhaps you should simply call createBitmap() instead? That one will respect the new size you tell it to be, and won't scale the original image, which is what you want from what i understood.
Please correct me if I'm wrong, however.
When a user clicks a button, i keep switching the background of a layout like this in Activity code:
...
mylayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.img1));//On First click
mylayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.img2));//On Second Click
mylayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.img10));//On 10th Click
...
mylayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.img1));//On 11th Click 1st image again and so on.
I have 10 images which i keep rotating.
Soon, it causes OutOfMemory exception. What am i doing wrong?
If it matters in my manifest file i have:
android:minSdkVersion="11"
android:targetSdkVersion="17" />
EDIT 1
Average size of the image is: 50K
Average dimension of the image is: 600x450
All images img1, img2 etc.. are jpeg images
Solution Update
Reducing image dimensions to 300x200 resolved the issue. The memory requirements went down significantly by this single change.
It might be happening because your image resources are in drawable folder; which is equivalent to drawable-mdpi. And your device might be something other than mdpi.
So, either provide images for all the screed densities you are gonna support. Or, put images in drawable-nodpi, so your images will not be resized. Hence no OutOfMemoryException.
Referred from here
Use Bitmap.recycle() if you can change your Drawable with Bitmap.
Hope This will help
Each bitmap with dimension 600x450 has size of 600*450*4 = 1 080 000 bytes (1 MB) in RAM.
You should load so large bitmaps in another way:
Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.img1);
myLayout.setBackgroundDrawable(new BitmapDrawable(resources, bitmap));
And before changing background of the view you have to do
Drawable background = myLayout.getBackground();
if (background != null && background instanceof BitmapDrawable) {
myLayout.setBackgroundDrawable(null);
BitmapDrawable bd = (BitmapDrawable) background;
bd.getBitmap().recycle();
}
I dont think changing background layout so frequently is a good idead. I suggest that you could use RelativeLayout with an ImageView as first item. Then everytime you need to change your background, you can change your ImageView with some cached library such as: Universal Image Loader
Have edited my question to make it more clear.
Basically I am working on an App where users need to upload their profile image. However I need to limit the size of the image upload to less than 4MB. Here where user selects his image from the image gallery, at that very instant I need to check the image file's size and show an alert to the user if the file size is greater than 4MB and restrict the upload.
Presently I am using this code to get the file size:
File Img = new File(selectedImage.getPath());
int length = Img.length();
I know length() returns file size in bytes, however even after conversion from bytes to MB, this always seems to return very small values than the original image file size leading to me believe that getting the file size this way is inaccurate.
Is there any other way I can get the file size of the image files from the device gallery?
Any help is much appreciated. Thanks guys.
Actually, the question is little bit unclear: size is 'in Kb' or youhave to know how would it seen on the screen??
Will that help?
Determining image sizes for multiple android screen sizes/densities
Android screen sizes in Pixels for ldpi, mdpi, hpdi?
Here's how to get dimensions from a drawable resource with BitmapFactory:
BitmapFactory.Options o = new BitmapFactory.Options();
o.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
Bitmap bmp = BitmapFactory.decodeResource("Your image file");
int w = bmp.getWidth();
int h = bmp.getHeight();
I do some operations with my Views that scale then trough:
imageView.setScaleX(2);
imageView.setScaleY(2);
On that ImageView I put a image that is two times greater than it, as expected when the scale is 1, the Bitmap is scaled down to the view size, my question is:
When the view is scaled up, the portion of the bitmap I see is the original image or the scaled-down image scaled up.
The difference is that if a tiny image is scaled up we lost contrast on letters, if it's the original image it wont.
When you scale an image up you're loosing quality. In you're case, you're doubling the width and height therefore, for each pixel you get 4 pixels assigned to it. The extra 3 pixels won't give you the quality you wanted.
If you want to go back to the original size you should decode the source again. This way you'll get the quality you're looking for.