This question already has an answer here:
Bitmap too large to be uploaded into a texture
(1 answer)
Closed 7 years ago.
In my android app, have all the images in the drawable folder. On most phones, we had no issue. But some phones have out of memory error. When the images copying for example to the drawable-xhdpi folder the issue is gone. What is the reason this problem, how can i fix it?
drawable is equivalent of drawable-mdpi
If you put your images in that folder they will get up-sampled for higher resolutions devices and that up-sampling can trigger OOM if images are large.
If you put same sized images in drawable-xhdpi you will have upsampled images only on larger xxhdpi devices, and downsampled on others.
If you want to avoid automatic up/down sampling of images put them in drawable-nodpi folder.
Different devices might have different size limits. Try using: drawable-xxhdpi
Helpful cheat sheet: http://i.stack.imgur.com/kV4Oh.png
For managing Out Of Memory Error one thing you may need to do is reduce the image size by compressing it and keep it in drawable folders.which is useful to reduce the app size and also memory consumption at runtime.
Or you may need to use the following class for reducing the image size aspect ratio.
ImageResizer
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight, boolean isLow) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
if (isLow) {
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* Calculate an inSampleSize for use in a {#link BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {#link BitmapFactory}. This implementation calculates
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
* having a width and height equal to or larger than the requested width and height.
*
* #param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* #param reqWidth The requested width of the resulting bitmap
* #param reqHeight The requested height of the resulting bitmap
* #return The value to be used for inSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// BEGIN_INCLUDE (calculate_sample_size)
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
long totalPixels = width * height / inSampleSize;
// Anything more than 2x the requested pixels we'll sample down further
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
// END_INCLUDE (calculate_sample_size)
}
Usage
private Bitmap mBackground;
private Drawable mBackgroundDrawable;
#Override
protected void onCreate(Bundle savedInstanceState) {
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.parent);
final Resources res = getResources();
int[] dimensions = Util.getDisplayDimensions(this);
mBackground = ImageResizer.decodeSampledBitmapFromResource(res, R.drawable.bg, 100, 100, false);
mBackgroundDrawable = new BitmapDrawable(res, mBackground);
linearLayout.setBackground(mBackgroundDrawable);
}
#Override
protected void onDestroy() {
recycle();
super.onDestroy();
}
private void recycle() {
if (mBackground != null) {
mBackground.recycle();
mBackground = null;
if (mBackgroundDrawable != null)
mBackgroundDrawable = null;
}
}
Note : If your applying true as third argument which help you to reduce the image size effectively using Bitmap.Config.RGB_565.
if (isLow) {
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
Finally, research about OOM.
Related
I'm trying to have live tile images in my application. I tested the functionality on some devices (API>22) and they work. But now i'm testing on API 22 and i'm getting the error in the title. I've searched through the site and I found this to be particularly helpful OutOfMemoryExceptionGridView. But I'm loading my images (straight from the res folder) to an array, then using a viewflipper to make the slideshow
How do I change my block of code (to fix the main OOME) since what I linked above uses bitmaps, and I am directly calling the res id.
This is my code:
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
int[] images = {R.drawable.img1, R.drawable.img2, R.drawable.img3}; //store images into array
viewFlipper = view.findViewById(R.id.firstTile); //main tile to have slideshow
for (int image : images) {
flipperImages(image); //performs the slideshow per image
}
}
public void flipperImages(int image) {
ImageView imageViewFirstTile = new ImageView(getContext());
imageViewFirstTile.setBackgroundResource(image);
viewFlipper.addView(imageViewFirstTile);
viewFlipper.setFlipInterval(12500);
viewFlipper.setAutoStart(true);
viewFlipper.setInAnimation(getContext(), android.R.anim.slide_in_left);
viewFlipper.setOutAnimation(getContext(), android.R.anim.slide_out_right);
}
How can I fix the main error with this implementation (calling the res id of the images directly)?
If the images can be scaled down to avoid loading the full sized images, I would consider that as a first step. There is no need to load the full-sized Bitmaps into memory if they window in which they're displayed is a fraction of the size. In your current approach, the images are not subsampled and as a result the entire Bitmap is loaded into memory at it's full size. So you have 3 full sized Bitmaps in memory.
For subsampling an image, see the following code. (Source: https://developer.android.com/topic/performance/graphics/load-bitmap)
/* reqWidth and reqHeight would be the dimensions of your ImageView or some other preferred dimensions. For example, if your Bitmap is 500 X 500 and your ViewFlipper/ImageView is only 100 X 100, passing 100 as reqWidth and 100 as reqHeight will subsample the image until it's dimensions are close to the 100 X 100. This will largely shrink the size loaded into memory. */
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);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
I am getting pretty frustrated with all these official Android docs that conveniently gloss over the fact that even when you apply a Bitmap to an ImageView, the ImageView's dimensions are 0, 0, and yet you can't get the dimensions prior to mapping, either!
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
All these methods assume you already know fixed dimensions for the ImageView's height and width in terms of pixels, but this is almost useless because in practice you're supposed to either use dp units or layout_weight so things scale to the viewable region so things look right on various phones.
The only way, it would seem, to get the dimensions is to go to the trouble of inefficiently mapping the bitmap and then getting the dimensions that way, but even if you do, the dimensions will show up as 0 if you do imageView.getWidth() or getHeight()! You have to do a bunch of weird async stuff to wait until the dimensions somehow "settle" and THEN you can finally get the dimensions, but at this stage, what's the point when you've already wasted time doing an inefficient mapping?
Is there some well known workaround to all this that Google isn't explaining in the docs? How are you supposed to know the ImageView dimensions when you're not working with pixels in the XML? It's mind-boggling to me that this isn't documented more.
Here is a sample of the problem:
<ImageView
android:id="#+id/my_imageview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
I have this ImageView inside a DialogFragment. The idea is that I can map a Bitmap to this ImageView and it will fit inside the DialogFragment by means of something like myImageView.setImageBitmap(BitmapFactory.decodeFile(path_to_file));, but this by itself is inefficient if the Bitmap is large.
However, in order to load the Bitmap efficiently, you need to already know the dimensions of the ImageView -- and in pixels! But you can't get the dimensions of the ImageView until you've already mapped the Bitmap inefficiently and waited for some kind of post-execute stage where the dimensions have settled in.
These are the methods I am using to do efficient mapping (assuming you already know the sizes -- it breaks if one of the dimensions is 0, so I added a fix). This code is from Google:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height == 0 || width == 0) {
return 0; //my fix
}
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromFilePath(String pathName, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
And for example this does not work:
myImageView.post(new Runnable() {
#Override
public void run() {
int reqHeight = myImageView.getHeight(); //height = 0
int reqWidth = myImageView.getWidth(); //width = 528
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picFullPath, options);
int realWidth = options.outWidth;
int realHeight = options.outHeight;
reqHeight = realHeight * reqWidth / realWidth; //reqHeight = 396, reqWidth = 528
Bitmap bm = decodeSampledBitmapFromFilePath(picFullPath, reqWidth, reqHeight);
myImageView.setImageBitmap(bm);
}
});
myImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int finalHeight = myImageView.getHeight(); //480!
int finalWidth = myImageView.getWidth(); //608!
}
});
You Should get height and Width of view after inflate any view from XML or Dynamically and then use bellow code to get rendered view height and width :
imageView.measure(0,0);
imageView.getMeasuredHeight();
imageView.getMeasuredWidth();
But Above method may given 0 for height and width , if you are not passing any height and width from XML or you are not used any relative property in Relative layout
I use this approach when I need to load bitmap after I know ImageView size:
myImageView.post(new Runnable() {
#Override
public void run() {
int requiredHeight = myImageView.getHeight(); //height is ready, but is 0 - not calculated.
int requiredWidth = myImageView.getWidth(); //width ready
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
int realWidth = option.outWidth;
int realHeight = option.outHeight;
//calculating required Height
requiredHeight = realHeight / realWidth * requiredWidth;
//now we have both - required height and width.
Bitmap bm = decodeSampledBitmapFromFilePath(pathName, requiredWidth, requiredHeight);
myImageView.setImageBitmap(bm);
}
});
I have bigs problems with the decodeSampleBitmap code and the final size of my images. I have my drawables in nodpi drawables folder for not multiply the same drawable in diferents folders with diferents sizes, and i resize after aply my decodeSampleBitmap.
I have this code for load a bitmap:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
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);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
The code i can get at the documentation. Ok i think this is for load images more eficiently but i have memory problems. I try explain this problem.
1-I want load a png, 2037x1440 pixels, 12.6kb.
2-I aply my code and load a png with 2037x1440 pixels and 11,73 MB. I dont undurstand what i'm doing wrong. Why android multiply for 1000 the size of my drawables?
10-12 12:06:07.842 12405-12822/com.viridesoft.kiseichuu V/control4: width=2037 height=1440 size=11733120
Edit: if i load the drawable with BitmapFactory.decodeResource the size is the same, 11733120.
The old question should be changed. I understand i cant reduce more my image size and i continue with the same problem.
I load a lot of images for create a dinamical background and charge all the characters(my player and zombies), in total until 111 bitmaps of 25%px x 10%px of the screen size and until 30 bitmaps of the 100% screen size. I load all this bitmaps in a loader activity and save for the later use in the game. I have a good quality of images and a drawable-nodpi folder with a lot of images for the best android resolution i see. My game run fluid in some devices (bq aquaris, nexus 5, etc) but is continually cleaning the memory and run slow in others (some tablets, some mid-range devices, etc )
The new question is:
How i can load eficiently a lot of images for a fluid game experiencie?
I have a set of image files stored in internal storage, each file size is about 750 KB. I need to create a 360 degrees animation like with this images, so, I load each image in a list, while I'm doing this process an out of memory exception appears.
I have been reading about bitmap processing on Android, but in this case is not about resize bitmap dimensions, the dimensions are OK (600, 450) because its a tablet application, is about image quality I think.
Is there a way to reduce the memory each bitmap takes?.
It is not possible without reducing image dimensions.
All images with the same dimensions require same amount of RAM, regardless it size on disk and compression. Graphics adapter don't understand different image types and compression and it needs only uncompressed raw array of pixels. And it size is constant
For example
size = width * height * 4; // for RGBA_8888
size = width * height * 2; // for RGB_565
So you should reduce image dimensions or use caching on the disk and remove bitmaps from the RAM that are currently invisible and reload from disk when needed.
There is a great resource on how to do this here:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Basically you need to load a bitmap at a different resolution using these two functions:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
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);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Then set the image as follows:
imageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),
R.drawable.my_image_resource,
imageView.getWidth(),
imageView.getHeight()));
Hope that helps!
This question already has answers here:
Strange OutOfMemory issue while loading an image to a Bitmap object
(44 answers)
Closed 8 years ago.
I am developing a game and I load 52 Bitmap files using BitmapFactory, and when running the App. I receive OutOfMemoryError. Is that because the images in the drawable folder are .JPG and not .PNG? Please let me know how to solve this issue.
JPG vs PNG doesn't matter, because when you load the bitmaps you uncompress them into raw data (basically the same as bmp). Uncompressed, each bitmap uses 4*width*height bytes. Depending on how big your images are, that can be very large. I would suggest using an LRUCache with a fixed size to hold only the images you actually need in memory and to kick out the unused ones.
I am dealing with 196 of png pictures in my app and ran into out of memory as well when I attempted to load all the images. My images are png, this means converting them from jpg to png didn't help.
I don't know if it's the optimum solution or not, but what I did is to start an IntentService from my main activity's OnCreate() method, first time it is called. Inside I check if a smaller version of the images is already stored on the internal storage. If the smaller versions are already created, I maintain a list of them and use them in my activities whenever I need to display them. If they are not present, which happens when the user installed the app for the first time, or the cache is cleared from the settings for this app, I create them by calling decodeSampledBitmapFromResource() with the desired width and height. The desired width and height can be store in an xml file and be specific for different screens. The functions I use for resizing while maintaining the image aspect are provided below.
/**
* Calculate in sample size.
*
* #param options the options
* #param reqWidth the req width
* #param reqHeight the req height
* #return the int
*/
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* Decode sampled bitmap from resource.
*
* #param res the res
* #param resId the res id
* #param reqWidth the req width
* #param reqHeight the req height
* #return the bitmap
*/
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);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}