I am trying to load an image in my app having size 495KB. If I load this image than heap size increases from 25MB to 35MB which is causing real memory issues in my app. If i dont load this image than heap size stays at 25MB. Can anybody tell why it is taking so much heap size?
Image is below
Code that I am using to load an image is
InputStream s4 = getResources().openRawResource(R.drawable.parallax_layer4);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
System.gc();
if(bitmap4 != null) {
bitmap4.recycle();
}
s3 = null;
System.gc();
bitmap4 = bitmap(s4);
layer4Back = new ImageView(this);
layer4Back.setImageBitmap(bitmap4);
layer4Back.setScaleType(ScaleType.FIT_XY);
parallaxLayout.addView(layer4Back, 3, lp);
try {
s4.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s4 = null;
System.gc();
private static Bitmap bitmap(final InputStream is) {
Bitmap bitmap = null;
System.gc();
Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inSampleSize = 1;
try {
// bitmap = BitmapFactory.decodeStream(is);
bitmap = BitmapFactory.decodeStream(is, null, options);
} catch (Error e) {
// TODO: handle exception
e.printStackTrace();
}
return bitmap;
}
Thanks
Ayaz Alavi
The image file is uncompressed when it's loaded into memory. At 5600 x 480 pixels and 32 bits per pixel, your image works out as almost exactly 10 MB when uncompressed.
My recommendation would be to cut it into smaller sections and only load the sections you need.
5,600px * 480px * 4 bytes = 10,752,000 bytes, so this isn't surprising.
There is some good guidance in the Displaying Bitmaps Efficiently article, as well as quite a few questions here on SO discussing good solutions.
Well what did you expect? The image is 5600 x 480 px, now in memory every single pixel takes 32 bits (plz correct me someone). Do the math and you get a good idea why it is a problem. You need to use a smaller image OR cut the image up in parts and load the parts that is needed when they are needed and discard the unnecessary parts.
I had a similar problem which is discussed here: Android app crash out of memory on relaunch
There is also a long discussion about the subject and possible solutions here: Strange out of memory issue while loading an image to a Bitmap object
BitmapFactory will auto-scale your bitmap if you load it and have a high DPI screen resolution. I think on devices with the largest DPI value it gets scaled by 2x in both directions.
This may explain some of the unexpected increase. Try putting your images in folder called drawable-nodpi and they will not be scaled.
Related
The app I'm creating requires a number of images to be pulled from our server, and displayed on a page. The user can go into several different categories, and each will have their own images. The problem is after going to 2-3 categories (depending on how many images are in those categories) in a row, the app has no more memory and cannot display the Bitmaps without crashing.
What I'd like to be able to do is clear the memory every time the user goes to a new category so that the old category's images won't be stored in memory anymore, freeing up space for the relevant category's images. I'm not sure if this is a good way to do it, or even how to do it if it was.
If anyone has a better solution let me know. One idea that was thrown around was loading only ~20 images at once, and waiting until the user scrolls to the bottom before loading more, however since our customers are paying to have their images on the app, that would cause less traffic to certain images, so this is not the ideal solution. However it's not out of the question.
Here is the code I'm using to load the images:
EDIT: My Mistake I posted the wrong code, this is the real code I'm using:
#SuppressWarnings("deprecation")
public Drawable loadImageFromWebOperations(String url, String imagePath) {
try {
if(Global.couponBitmaps.get(imagePath) != null){
scaledHeight = Global.couponBitmaps.get(imagePath).getHeight();
return new BitmapDrawable(getResources(), Global.couponBitmaps.get(imagePath));
}
Drawable d = null;
File f = new File(getBaseContext().getFilesDir().getPath().toString() + "/" + imagePath + ".png");
if (f.exists()) {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
int scaledWidth = 0;
try {
display.getSize(size);
scaledWidth = size.x;
} catch (java.lang.NoSuchMethodError ignore) {
scaledWidth = display.getWidth();
}
Bitmap bitmap = null;
BitmapScaler scaler = new BitmapScaler(f, scaledWidth);
bitmap = scaler.getScaled();
scaledHeight = bitmap.getHeight();
d = new BitmapDrawable(getResources(), bitmap);
Global.couponBitmaps.put(imagePath, bitmap);
} else {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
int scaledWidth = 0;
try {
display.getSize(size);
scaledWidth = size.x;
} catch (java.lang.NoSuchMethodError ignore) {
scaledWidth = display.getWidth();
}
Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(url).getContent());
int height = bitmap.getHeight();
int width = bitmap.getWidth();
scaledHeight = (int) (((scaledWidth * 1.0) / width) * height);
f.getParentFile().mkdirs();
f.createNewFile();
OutputStream output = new FileOutputStream(f);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, output);
output.close();
bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false);
d = new BitmapDrawable(getResources(), bitmap);
Global.couponBitmaps.put(imagePath, bitmap);
}
return d;
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (OutOfMemoryError e){
e.printStackTrace();
return null;
}
}
If anyone knows if there is a more efficient way of loading the images, or if there is a way to clear the memory before drawing them, it would be greatly appreciated, thank you.
Since you are loading your Bitmaps from a server, you should probably use an image loading library.
Powerful libraries are for example:
Picasso, by square
UniversalImageLoader, by nostra13
They allow you to do pretty much anything with your Bitmap and during loading. Chaching is also supported.
UniversalImageLoader is slightly more powerful, Picasso is easier to use, in my opinion.
Bitmaps in android are a bit tricky.. My first app which required a large number of images in a gridview - I ran into a lot of OOM problems as well.
I ended up using nostra13's "Universal Image Loader" as it seemed to be the best solution for what I needed. It has a lot of built in features such as disk cache, memory cache, bitmap size, thread pool size, image scaling, etc. There are working examples too. :)
Nostra13 Universal Image Loader
There are a few things to keep in mind:
Recycle your bitmaps before displaying the next one else they will
pile up in memory and you'll get OOM
If using Universal Image Loader, make sure you use .bitmapConfig(Bitmap.Config.RGB_565) as it uses the least amount of memory per image.
If you plan on displaying a lot of images in a gridview or listview, the approach I used is to load two different images from your APIs, one being extremely small (100x100 ish) for thumbnail view, and the other being the full size image. This way you wont run out of memory when showing the thumbnails. Then only when the user clicks a thumbnail, will it load the the full size image for that position.
Hopefully this helps. :)
Every time you create new Drawable and BitmapDrawable use one Drawable and refresh image on it.
I am trying to take a picture using the Android camera. I have a requirement to capture a 1600 (w) x 1200 (h) image (3rd party vendor requirement). My code seems to work fine for many phone cameras but the setPictureSize causes a crash on some phones (Samsung Galaxy S4, Samsung Galaxy Note) and causes a streaked picture on others (Nexus 7 Tablet). On at least the Nexus the size I desire is showing up in the getSupportPictureSizes list.
I have tried specifying the orientation but it didn't help. Taking the picture with the default picture size works fine.
Here is an example of the streaking:
For my image capture I have a requirement of 1600x1200, jpg, 30% compression, so I am capturing a JPG file.
I think I have three choices:
1) Figure out how to capture the 1600x1200 size without a crash or streaking, or
2) Figure out how to change the size of the default picture size to a JPG that is 1600x1200.
3) Something else that is currently unknown to me.
I have found some other postings that have similar issues but not quite the same. I am in my 2nd day of trying things but am not finding a solution. Here is one posting that got close:
Camera picture to Bitmap results in messed up image (none of the suggestions helped me)
Here is the section of my code that worked fine for until I ran into the S4/Note/Nexus 7. I have added a bit of debugging code for now:
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
int pictureWidth = 1600;
int pictureHeight = 1200;
// testing
Camera.Size test = parameters.getPictureSize();
List<Camera.Size> testSizes = parameters.getSupportedPictureSizes();
for ( int i = 0; i < testSizes.size(); i++ ) {
test = testSizes.get(i);
}
test = testSizes.get(3);
// get(3) is 1600 x 1200
pictureWidth = test.width;
pictureHeight = test.height;
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setPictureSize(pictureWidth, pictureHeight);
parameters.setJpegQuality(30);
parameters.setPreviewSize(size.width, size.height);
// catch any exception
try {
// make sure the preview is stopped
mCamera.stopPreview();
mCamera.setParameters(parameters);
didConfig = true;
catch(Exception e) {
// some error presentation was removed for brevity
// since didConfig not set to TRUE it will fail gracefully
}
}
Here is the section of my code that saves the JPG file:
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
if ( data.length > 0 ) {
String fileName = "image.jpg";
File file = new File(getFilesDir(), fileName);
String filePath = file.getAbsolutePath();
boolean goodWrite = false;
try {
OutputStream os = new FileOutputStream(file);
os.write(data);
os.close();
goodWrite = true;
} catch (IOException e) {
goodWrite = false;
}
if ( goodWrite ) {
// go on to the Preview
} else {
// TODO return an error to the calling activity
}
}
Log.d(TAG, "onPictureTaken - jpeg");
}
};
Any suggestions on how to correctly set up the camera parameters for taking photos or how to crop or resize the resulting photo would be great. Especially if it will work with older cameras (API level 8 or later)! Based on needing the full width of the picture I can only crop off the top.
Thanks!
EDIT: Here is what I ended up doing:
I started by processing the Camera.Parameters getSupportedPictureSizes to use the first one that had the height and width both greater than my desired size, AND the same width:height ratio. I set the Camera parameters to that picture size.
Then once the picture was taken:
BitmapFactory.Options options = new BitmapFactory.Options();;
options.inPurgeable = true;
// convert the byte array to a bitmap, taking care to allow for garbage collection
Bitmap original = BitmapFactory.decodeByteArray(input , 0, input.length, options);
// resize the bitmap to my desired scale
Bitmap resized = Bitmap.createScaledBitmap(original, 1600, 1200, true);
// create a new byte array and output the bitmap to a compressed JPG
ByteArrayOutputStream blob = new ByteArrayOutputStream();
resized.compress(Bitmap.CompressFormat.JPEG, 30, blob);
// recycle the memory since bitmaps seem to have slightly different garbage collection
original.recycle();
resized.recycle();
byte[] desired = blob.toByteArray();
Then I write out the desired jpg to a file for upload.
test = testSizes.get(3);
// get(3) is 1600 x 1200
There is no requirement that the array have 4+ elements, let alone that the fourth element be 1600x1200.
1) Figure out how to capture the 1600x1200 size without a crash or streaking
There is no guarantee that every device is capable of taking a picture with that exact resolution. You cannot specify arbitrary values for the resolution -- it must be one of the supported picture sizes. Some devices support arbitrary values, while other devices will give you corrupted output (as is the case here) or will flat-out crash.
2) Figure out how to change the size of the default picture size to a JPG that is 1600x1200
I am not aware that there is a "default picture size", and, beyond that, such a size will be immutable, since it is the default. Changing the picture size is your option #1 above.
3) Something else that is currently unknown to me.
For devices that support a resolution that is bigger on both axes, take a picture in that resolution, then crop to 1600x1200.
For all other devices, where one or both axes are smaller than desired, take a picture in whatever resolution suits you (largest, closest match to 4:3 aspect ratio, etc.), and then stretch/crop to get to 1600x1200.
I make use of the following code to set my static images as wallpaper from my android app.. The image dimensions are like 425*700, 280*180, 600*400 etc., so the image dimensions are not the same.
try {
WallpaperManager myWallpaperManager = WallpaperManager
.getInstance(context);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int fullWidth = size.x;
int fullHeight = size.y;
// int fullWidth = wManager.getDesiredMinimumWidth();
// int fullHeight = wManager.getDesiredMinimumHeight();
Log.d("Debug", Integer.toString(fullWidth));
Log.d("Debug", Integer.toString(fullHeight));
Bitmap bitmap = BitmapFactory.decodeStream(getResources()
.openRawResource(R.drawable.hello));
Bitmap bitmapResized = Bitmap.createScaledBitmap(bitmap, fullWidth,
fullHeight, true);
myWallpaperManager.suggestDesiredDimensions(
bitmapResized.getWidth(), bitmapResized.getHeight());
myWallpaperManager.setBitmap(bitmapResized);
} catch (IOException e) {
e.printStackTrace();
}
But the images are quite streched and doesn't look good after setting as a wallpaper in the phone.. What am I doing wrong?
I guess problem lies in the resources as you have already mentioned. For a good view/quality of wallpaper we have to use different resources of images as according to the resolution of your device, if you want to use your application of setting the wallpaper to be used by many devices without making any changes(an apk). The crux is, you need a better quality image matching the resolution of your device. Hope it helps.
Edit: The distortion is again because you are resizing your image for covering full screen size. I guess if you do not resize it, it might work, though I havent use it till now.
I am in the process of implementing expansion files for my Android app.
So far, I've placed several images and audio files into the main expansion file (I am not using the patch expansion file). None of the files have been compressed.
Through the app, I am able to download the expansion file, and play the audio files without any problems. At the same time an audio file in the expansion file is played, I am also displaying an image from the expansion file. However, the image is considerably smaller than I expected.
The image is 320x400px. Before implementing the expansion files, it was displayed as expected in my app. However, after implementation, it looks like the image shrank to about 50px wide (the height shrank in proportion).
I then tried the solution offered in How to create a drawable from a stream without resizing it. While the image does appear slightly larger, it is still much smaller than what I want it to be (looks like it's about 100x125 px now). Currently, my code for displaying the image looks like this:
public void displayImageFromExpansionFile(){
Bitmap b = BitmapFactory.decodeStream(fileStream);
b.setDensity(Bitmap.DENSITY_NONE);
Drawable d = new BitmapDrawable(this.getResources(), b);
imageToDisplay.setImageDrawable(d);
}
public void showImg(int imgNum){
switch(imgNum){
case(1):
try{
if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL){
fileStream = expansionFile.getInputStream("filepath inside expansion file for small image");
}
else if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL){
fileStream = expansionFile.getInputStream("filepath inside expansion file for normal image");
}
else if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE){
fileStream = expansionFile.getInputStream("filepath inside expansion file for large image");
}
else{
fileStream = expansionFile.getInputStream("filepath inside expansion file for xlarge image");
}
displayImageFromExpansionFile();
} catch (Exception e) {
e.printStackTrace();
}
break;
// more cases here
}
It still seems as if the image is not being displayed at its actual size. When I examine the image inside the expansion file, I can see that it is still at 320x400px. However, the app is not displaying the image at these dimensions.
What could I do to get the app to display the image at its correct dimensions?
Thanks!
---UPDATE---
I've also tried the code below, with no difference in results. It still looks to be about 100x125px, instead of 320x400px, like its original size.
public void displayImageFromExpansionFile(int bWidth, int bHeight){
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap b = BitmapFactory.decodeStream(fileStream, null, bfo);
b.setDensity(Bitmap.DENSITY_NONE);
imageToDisplay.setImageBitmap(b);
}
The only thing that's worked so far is Bitmap.createScaledBitmap(b, bWidth, bHeight, true);
On my phone, doubling the image's original dimensions (to 640x800 px) using the above method brings the image up to its expected size, but I would imagine that the image might appear at different sizes on different phones (probably because of screen density/size). When I tried doubling the xlarge image dimensions and viewed it on my tablet, the image appears larger than it should.
In the end, I fiddled around with the layout XML file that was displaying the shrunken image. I changed the attributes of the imageView like so:
Width / Height: fill_parent (both)
Scale Type: fitCenter
Now, the images are slightly bigger than I initially expected, but I think it looks better this way. And now the images are of a consistent size. Problem solved!
If anyone else is having trouble with what I've been experiencing, hope this helps.
thats worked for me:
public static Bitmap getBitmapExt(Context context, String name){
Bitmap bitmap = null;
try {
if(expansionFile == null){
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, pInfo.versionCode, 0);
}
InputStream is = expansionFile.getInputStream("images/" + name + ".png");
bitmap = BitmapFactory.decodeStream(is);
}
catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
https://gist.github.com/uebi-belli/3c07323c8055a3b66ccac0d388b2b013
Hope that helps =)
I'm having an odd problem with my map pin sizes. To preserve dynamic-ness, the map pins for different categories are stored on a site's server so that they can be changed at any point even after the app is published.
I'm caching the pins every time I download them and I only ever re-download them if the server sends back a bit saying that one has changed since last I downloaded it. The first time I grab the pins, I use the bitmaps before I save them to files and the map markers are the correct size. Every time after that I'm loading a saved version of the pins straight from the image file. These are displaying considerably smaller than they are when using the bitmaps from the first download.
At first, I thought it was a problem with the way I'm saving the PNGs, but their sizes are correct (64 x 64). Is this a dip/px issue or do I need to decompress the image files with some sort of option?
Here's how I grab the images the first time:
public static Bitmap loadMapPin(String category, int width, int height) {
URL imageUrl;
category = category.toLowerCase().replace(" ", "");
try {
imageUrl = new URL(PIN_URL+category+".png");
InputStream is = (InputStream) imageUrl.getContent();
Options options = new Options();
options.inJustDecodeBounds = true; //Only find the dimensions
//Decode without downloading to find dimensions
BitmapFactory.decodeStream(is, null, options);
boolean scaleByHeight = Math.abs(options.outHeight - height) >= Math.abs(options.outWidth - width);
if(options.outHeight * options.outWidth >= width * height){
// Load, scaling to smallest power of 2 that'll get it <= desired dimensions
double sampleSize = scaleByHeight
? options.outHeight / height
: options.outWidth / width;
options.inSampleSize =
(int)Math.pow(2d, Math.floor(
Math.log(sampleSize)/Math.log(2d)));
}
options.inJustDecodeBounds = false; //Download image this time
is.close();
is = (InputStream) imageUrl.getContent();
Bitmap img = BitmapFactory.decodeStream(is, null, options);
return img;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And here's how I'm loading them from the cached file:
BitmapFactory.decodeFile(filepath);
Thanks in advance!
I've found that, by default, decompressing an image to a bitmap doesn't scale with high density screens. You have to set the density to none. In other words, you specify that the image is meant for an unknown density.
Solution:
Bitmap b = BitmapFactory.decodeFile(filepath);
b.setDensity(Bitmap.DENSITY_NONE);