18

I'm working on an app that uses large images (1390 ×ばつ 870 : 150kb - 50kb). I'm adding images as I tap a trigger/ImageView.

At a certain point I'm getting an out of memory error:

java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)

To resize the image I'm doing this:

Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
 imageStream = new FileInputStream(imageLoc);
 productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
 productIV.setImageBitmap(productIndex);
 } catch (FileNotFoundException e1) {
 // TODO Auto-generated catch block
 e1.printStackTrace();
 }
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(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 / 3;
 final int halfWidth = width / 3;
 // 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 got this way of resizing to save space from the Android Docs: Loading Large Bitmaps Efficiently

According to the log this like is the culprit in the decodeSampledBitmapFromResource method :

return BitmapFactory.decodeFile(resId, options);

----- edit ----- Here is how I'm adding each item to the FrameLayout.

for(int ps=0;ps<productSplit.size();ps++){
 //split each product by the equals sign
 List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));
 String tempCarID = productItem.get(0);
 tempCarID = tempCarID.replace(" ", "");
 if(String.valueOf(carID).equals(tempCarID)){
 ImageView productIV = new ImageView(Configurator.this);
 LayoutParams productParams = new LayoutParams(
 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
 productIV.setId(Integer.parseInt(partIdsList.get(x)));
 productIV.setLayoutParams(productParams);
 final String imageLoc = productItem.get(2);
 InputStream imageStream;
 try {
 imageStream = new FileInputStream(imageLoc);
 productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
 productIV.setImageBitmap(productIndex);
 } catch (FileNotFoundException e1) {
 // TODO Auto-generated catch block
 e1.printStackTrace();
 }
 productLayers.addView(productIV);
 }
}
asked Jan 27, 2014 at 22:24
7
  • Once you've loaded a sampled bitmap, do you ever remove it from your View/Adapter when it is no longer visible? Commented Jan 27, 2014 at 22:27
  • it's always visible. I'm running a loop to add items to a FramedLayout. I'll updated my question to show this. Commented Jan 27, 2014 at 22:29
  • I've updated the question. If I recycle the productIndex Bitmap because it destroys the image and not in my FrameLayout anymore. Commented Jan 27, 2014 at 22:36
  • But visually what does this look like, are you overlapping bitmaps (which could be wasteful)? How many bitmaps are you loading up? Commented Jan 27, 2014 at 22:40
  • Yes, I'm having to overlay the bitmaps. This layers images to create a solid image that the user will them send to themselves or others. The user will be able to add as many as needed... Commented Jan 27, 2014 at 22:47

6 Answers 6

31

You can use another bitmap-config to heavily decrease the size of the images. The default is RGB-config ARGB8888 which means four 8-bit channels are used (red, green, blue, alhpa). Alpha is transparency of the bitmap. This occupy a lot of memory - imagesize X 4. So if the imagesize is 4 megapixel 16 megabytes will immidiately be allocated on the heap - quickly exhausting the memory.

Instead - use RGB_565 which to some extent deteriorate the quality - but to compensate this you can dither the images.

So - to your method decodeSampledBitmapFromResource - add the following snippets:

 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;

In your code:

 public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
 // First decode with inJustDecodeBounds=true to check dimensions
 final BitmapFactory.Options options = new BitmapFactory.Options();
 options.inJustDecodeBounds = true;
 BitmapFactory.decodeFile(resId, options);
 // Calculate inSampleSize
 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 // Decode bitmap with inSampleSize set
 options.inJustDecodeBounds = false;
 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;
 return BitmapFactory.decodeFile(resId, options);
 }

References:

http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888

answered Jan 27, 2014 at 23:55
Sign up to request clarification or add additional context in comments.

3 Comments

what benefit does setting inSampleSize have?
BitmapFactory.Options.inDither is deprecated in Android N, you know some alternative to this?
@EmersonDallagnol ... I wish I could answer that. I am a bit surprised the flag is deprecated. I'll look into this more in due course of time
26

High resolution devices such as S4 usually run out of memory if you do not have your image in the proper folder which is drawable-xxhdpi. You can also put your image into drawable-nodpi. The reason it would run out of memorey if your image just in drawable that the android would scale the image thinking that the image was designed for low resolution.

answered Jan 27, 2014 at 22:49

8 Comments

the images being used are downloaded via an API and stored in the device sd storage
It is still possible that they are getting adjusted for high res, maybe you can dig in that direction, I'll research as well and let you know what I find out
as far as the image themselves: they are 72 dpi and compressed via photoshop using "export for web and devices". The iOS and Android app both use the same API and Images. The reason images are so big is to account for Retina.
Is this worth looking into: Caching Bitmaps
You guys don't have to look if you don't think this is the problem, but I'm going to research. In my experience every time I had oom related to images was the problem I described above
|
6
answered Jul 14, 2015 at 18:40

Comments

3

Here is how I'm adding each item to the FrameLayout that's the problem, the code keep adding and adding more images, and doesn't matter how well you resize or how much memory the device have, at certain point it WILL run out of memory. That's because every image you add it's keeping in memory.

For this type of situation what the apps do is to use a ViewGroup that can recycle views. I don't know your layout, but usually is a ListView, GridView or a ViewPager, by recycling views you re-use the layout and can dispose re-load images as necessary.

For the specific purpose of loading and resizing images I strongly advise use Picasso library as it is VERY well written, simple to use and stable.

answered Jan 27, 2014 at 23:12

2 Comments

Yeah, that's what I thought was happening. Basically, I have a frame layout with sets of images (all the same size) that need to lay on top of each other. For each image I'm using an ImageView. I thought about just keeping an ArrayList of all my image locations and everytime a new layer is added flatten all the layers into one bitmap image instead of add new layers for each image.
that probably would work. But if you just keep adding them, it will always run out of memory.
0

You are still going to need to manage the bitmap memory as I wouldn't try to allocate a total space more than 3x the size of the screen (if you think about it makes sense for scrolling behavior). If you are overlaying one image on top of another, at some point, you're hitting an Out Of Memory error. You may need to look at capturing the prior screen image as a single background image to make sure you still fit within the available memory. Or when a new image overlaps an existing image only load and render the visible portion. If performance becomes an issue, then you may need to consider OpenGL Textures but the memory allocation problem is still the same.

Do go through all of the Displaying Bitmaps Training as it should give you some additional ideas of how to handle display.

answered Jan 27, 2014 at 23:05

6 Comments

I thought about taking a "current state" of the FrameLayout -> flattening the Bitmaps into one image -> replace the current view. The flattened image would get bigger though. but I wouldn't just be stacking layers like I am now. Does that sound more doable or is roughly the same thing?
It sounds like what I was suggesting, I would still keep in mind that you don't have an arbitrary large bitmap memory budget. If the background image is scrollable, then you'll have to even play more games to keep it within budget or change the limits the of the app.
Nothing is scrollable. I eventually have to push this to an API so I'll have to flatten it anyhow.
Then at most your background image would be one screen size big and you'll be well under 3x provided everything else is unloaded.
So, based on these comments does this sound better: load base layers into ArrayList -> Flatten layers -> place into layout as one bitmap... user adds layer: add new layer to ArrayList -> remove current bitmap -> Flatten layers again -> place new bitmap into layout. Just replace the bitmap area with a progress bar as it flattens the image.
|
0

Use Fresco library to load large images will avoid this error. in xml layout

<com.facebook.drawee.view.SimpleDraweeView
 android:id="@+id/my_image_view"
 android:layout_width="1300dp"
 android:layout_height="1300dp"
 fresco:placeholderImage="@drawable/my_drawable"
 />

and in javacode

Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
answered May 8, 2016 at 11:53

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.