In my Android app I have a custom ImageView which, based on user interaction, needs to update its image. The ImageView can only have two images, which represent the back and front of a card.
So, when the user clicks a button I need to show the back of the card (set my custom's ImageView image to the back of the card and when the user cliks the button again, set the front image as ImageView drawable).
In order to accomplish this I have two methods in my custom ImageView:
public void showBack() {
setImageResource(R.drawable.card_back);
}
public void showFront() {
setImageResource(R.drawable.card_front);
}
Of course, this works great, but the memory consumption of my app sometimes get near 114 MB, and it increases as the user clicks the button (rotates the card).
Due to that, I think the problem could be produced by the change of image.
The images are small, one is 104.6KB and the other 80.4KB (PNG format), so I don't understand why my app is consuming so much memory (maybe because images get allocated very times and never recycled?, or maybe because of Android's cache system of drawables?)
So, what can I do to solve my problem?
Thank you.
Image dimensions are 335 x 501
So there are a few factors that come into play here. The imageView dimensions (I assume) have been defined in dp . The number of pixels this translates to changes from phone to phone depending on the screen resolution. Hence the same drawable, when attached to imageView in different phones will have to be scaled to match the px required to display it in the imageView.
This scaling requires large amount of memory which causes the OOM exception. Some phones have a large RAM and can complete this scaling without causing any problems, whereas older models may not be able to.
When you load in the imageView, the original image is kept in memory and then rescaled. I suggest getting the pixel dimensions of the imageView and then scaling the image accordingly, to produce the best fit. This will reduce the memory consumption.
This answer and this should help you in getting the dimensions of the imageView.
Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
R.drawable.icon_resource);
private Bitmap ScaleImage(Bitmap bitmap){
//the original dimension of the bitmap
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int requiredWidth=0;
int requiredHeight=0;
//todo: Calculate the required dimensions of the bitmap as per the imageView
ExifInterface exif;
Matrix matrix = new Matrix();
/* In case you need to rotate the bitmap
try {
exif = new ExifInterface(value);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
Log.d("EXIF", "Exif: " + orientation);
if (orientation == 6) {
matrix.postRotate(90);
Log.d("EXIF", "Exif: " + orientation);
} else if (orientation == 3) {
matrix.postRotate(180);
Log.d("EXIF", "Exif: " + orientation);
} else if (orientation == 8) {
matrix.postRotate(270);
Log.d("EXIF", "Exif: " + orientation);}
}catch (IOException e){e.printStackTrace();}
*/
matrix.postScale(requiredWidth/originalWidth, requiredHeight/originalHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,width, height, matrix, true);
return resizedBitmap;
}
And finally
public void showFront() {
Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
R.drawable.icon_resource);
Bitmap scaledBitmap = ScaleImage(icon);
setImageResource(scaledBitmap);
}
Now ideally you would want to shift the scaling of the image to an Async Task so the main thread is not blocked.
Let me know if this works please.
The size of the images don't play much of a role here. The dimensions on the other hand do. So if you are using images with large dimensions and using them in ImageView with smaller resolution, then it will be a problem and you might end up having OOM (Out Of Memory Exception).
Please see:
http://developer.android.com/training/displaying-bitmaps/index.html
From my personal experience when loading images you should always use picasso library. It helps with all the memory problems, lets you scale easily etc.
http://square.github.io/picasso/
Related
I'm doing a gallery image. I get path of image in device and parse to URI.
Then I use Picasso Android Lib to load image into Imageview in Gridview. It's work fine until have a large image. Picasso can not load large image. I got error Out Of Memory. Is there any suggestion to load large image into ImageView? And have any lib to load image into ImageView can instead Picasso?
I found ImageLoader lib for my problem. It works fine. I tested on my project, and then I saw that ImageLoader looks better than Picasso.
Learn how to use common techniques to process and load Bitmap objects in a way that keeps your user interface (UI) components responsive and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly consume your available memory budget leading to an application crash due to the dreaded exception:
java.lang.OutofMemoryError: bitmap size exceeds VM budget.
There are a number of reasons why loading bitmaps in your Android application is tricky:
Mobile devices typically have constrained system resources. Android devices can have as little as 16MB of memory available to a single application. The Android Compatibility Definition Document (CDD), Section 3.7. Virtual Machine Compatibility gives the required minimum application memory for various screen sizes and densities. Applications should be optimized to perform under this minimum memory limit. However, keep in mind many devices are configured with higher limits.
Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
Android app UI’s frequently require several bitmaps to be loaded at once. Components such as ListView, GridView and ViewPager commonly include multiple bitmaps on-screen at once with many more potentially off-screen ready to show at the flick of a finger.
Read More Regarding this issue
Here is an example I once used, but it is not perfect! (Sorry)
You can reduce the bitmap size:
public Bitmap resizeBitmap(Bitmap bitmap) {
if (bitmap.getHeight() > 4096 || bitmap.getWidth() > 4096) {
int width = (int) (bitmap.getWidth() * 0.9);
int height = (int) (bitmap.getHeight() * 0.9);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
resizeBitmap(resizedBitmap);
returnresizedBitmap;
} else {
return bitmap;
}
}
If I should do it again: (not tested)
public Bitmap resizeBitmap(Bitmap bitmap) {
int originalWidth = bitmap.getWidth();
int originalHeight = bitmap.getHeight();
if (originalWidth > 4096 || originalHeight > 4096) {
int height;
int width;
if(originalHeight > originalWidth) {
height = 4096;
width = originalWidth / (originalHeight / 4096);
} else {
width = 4096;
height = originalHeight / (originalWidth / 4096);
}
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
return resizedBitmap;
} else {
return bitmap;
}
}
You can custom recent-images library for your purpose. It's very simple and easy to use library. It creates a thumbnail of image for showing in gridview and then in click opens the original.
the title sounds maybe a bit like a "noob question" but I know quite well how to program for Android, I just to figure out what it is the best way to achieve what I want.
My use case is: the user takes a photo and sends it to our server which have a file-size limit (which could mean that we have to resize the photo directly on the device).
Seems easy, right? My problem are the following:
1) Better use intents which could crash because some camera apps are coded with the ass or build a basic view "take photo and confirm" with cawc camera libs ? (I did the two, I prefer intents but I'd like to have an opinion on that).
2) How do you handle the file size limit? I mean getting the size of the photo is quite easy with the File.length() (even if the returned value is not perfectly right) but if you goes over the limit, how can you say how big will be the resized picture? (you need to convert in bitmap to resize it and it's then a lot of problems with OOMException and you cannot calculate final size of a bitmap on the disk, you need to compress and write it to the disk and analyse the newly created file after).
Thanks for help :D
I did the same thing before.
1.I use intent to call the other camera app, and inside onActivityResult,
I get back the URI and process it as I need.
We do resize the pic, but I also keep the original ratio, and rotate it based on exif data.
Hopefully this resizing code block can give you some hints.
public static Bitmap DecodeImage(String path, int resolution) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
opts.inSampleSize = computeSampleSize(opts, -1, resolution);
opts.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, opts);
}
public static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,
maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == -1) &&
(minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
The solution is not fancy but it is what I did it in the project, and we so far have no problems with it after release.
There are lots of question over this topic and hope you searched for it.
Q) Better use intents which could crash because some camera apps are coded with the ass or build a basic view "take photo and confirm" with cawc camera libs ? (I did the two, I prefer intents but I'd like to have an opinion on that).
A) Intents are best because they are build in, some device manufacturers intents for cropping and re-sizing images and I face similar problem of not having that on same manufacturer but on Older device. A stable/relible third party app would suffice for basic operations.
Q) How do you handle the file size limit? I mean getting the size of the photo is quite easy with the File.length() (even if the returned value is not perfectly right) but if you goes over the limit, how can you say how big will be the resized picture? (you need to convert in bitmap to resize it and it's then a lot of problems with OOMException and you cannot calculate final size of a bitmap on the disk, you need to compress and write it to the disk and analyse the newly created file after).
A)
How do you handle the file size limit?
Size limit depends on Camera, if you have 10MP camera then the resultant size would be greater than 5MP (hope you get the part).
You need to convert in bitmap to resize it and it's then a lot of problems with OOMException and you cannot calculate final size of a bitmap on the disk You can calculate the image size or sample it or crop it or resize it as far as you keep best practices of Android intact, recycle the bitmap as soon as you done with the bitmap operations. I have a app which has 100s of Images and most of them being send to server and some times it throws OOM, then I handle it accordingly.
You can gone through the link Loading Large Bitmap
They explained really well regarding bitmap scaling.
Please let me know if you don't get any point in this link.
Thanks
I have searched and found simple code to rotate an image. I am pulling the image out of an ImageView object into a bitmap, rotating it then putting it back. I realize this is not the most effective method but I don't think it should crash without giving an error message in the CATCH block.
Here is my code. The only value passed in is "r" or "l" depending on which direction I want to rotate. Smaler images (1500x1500 or smaller) work just fine. Things go bad around the 2500x2500 size.
public void rotate(String dir)
{
try
{
float angle = (dir.equals("r") ? 90 : -90);
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Matrix matrix = new Matrix();
matrix.reset();
matrix.postRotate(angle);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
imageView.setImageBitmap(bitmap);
}
catch(Exception e)
{
Utilities.logError(e.toString());
}
}
Any clue as to why it is crashing and why it doesn't thow an exception? I just get a message "Unfortuantly process .... has stopped" and I get kicked back to the welcome screen of my app.
Oh, for kicks I set the angle to ZERO (hard coded) and it didn't crash. I suspect that it is just taking too long to rotate and Android is having a fit. But I am not sure how to confirm that as the problem or how to tell Android to wait a little longer.
Even if I reduce the preview image for the rotation, when I go to save I will have to rotate the full size image at least once and will hit this same issue. Won't I?
I can more or less guarantee without looking at the logs that you're getting an Out Of Memory Exception.
You need to use smaller images, or use a different method to rotate that doesn't use up so much memory (you're allocating 2 2500x2500 bitmaps at the same time here! that's tons!).
Try using a RotateAnimation to get your effect instead.
Hope this helps :)
In my app I am giving a functionality to edit an image from the filepath.
Now I want to add a feature to change the image orientation with animation and then save it.
I am able to change the orientation but don't know how to add animation to it.
Here's my code.
ImageView img;
Bitmap bmp;
Bitmap rotatedBMP;
String imageFilePath="";
File imgFile;
public void rotate(){
if(rotatedBMP == null){
if(imgFile != null && imgFile.exists())
bmp = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
}else
bmp = rotatedBMP;
// Getting width & height of the given image.
int w = bmp.getWidth();
int h = bmp.getHeight();
Matrix mtx = new Matrix();
mtx.preRotate(90);
rotatedBMP = Bitmap.createBitmap(bmp, 0, 0, w, h, mtx, true);
/*BitmapDrawable bmd = new BitmapDrawable(rotatedBMP);
img.setImageDrawable(bmd);*/
img.setImageBitmap(rotatedBMP);
}
There's a conflict between your desire to animate rotate a view, and your desire to rotate and save the actual bitmap.
If you want to rotate the image view, the good way to do that is to rotate the canvas in an onDraw method with an animation update step, or to use Android's helper for that like RotateAnimation. Drop us a comment if you want examples.
That's the "good way" because it doesn't create a new bitmap on every animation frame, which would be very expensive.
However, you've got a conflict that you also want to rotate the bitmap itself, rather than just the view, so as to save it. This is fine given it's not on every animation step.
You consequently will have to save the user's chosen transformations you want to apply to the bitmap as late as possible. Then animate the view however is appropriate in response to the user. On the save, you'd concat all the users transformations on writing to the file. Or give up on the idea of animating it.
My implementation idea would be to store a second matrix locally and have each user input both update the canvas transforms and that matrix. You'd then 'get hold' of the bitmap as it were when the save button is pressed and apply it to the updated transformation matrix.
Happy to elaborate as requested.
Some sources
Android: Rotate image in imageview by an angle (mentions issues with creating many bitmaps too)
Rotating a view in Android (gauravjain0102's answer is underrated!)
My app is using camera to take photos. The problem is, the photo is rotated by 90 degrees. The app is designed to run in portrait orientation and I have set
android:configChanges="orientation|screenSize"
to avoid orientation changes. I thought I managed to fix it with
parameters.setRotation(90);
but it turns out that it varies on different devices (tested on lenovo ThinkPad tablet and a copule of smartphones). I tried reading the EXIF of the photo but orientation is not included there. I know there are many similar posts but most of them regards default camera app. Could someone explain me what this problem is caused by and how can i fix it? Thanks in advance.
Try this for getting image as u wanted
public static Bitmap createRotatedBitmap(Bitmap bm, float degree) {
Bitmap bitmap = null;
if (degree != 0) {
Matrix matrix = new Matrix();
matrix.preRotate(degree);
bitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),
bm.getHeight(), matrix, true);
}
return bitmap;
}
bitmap = createRotatedBitmap(bitmap, 90);
Yes, orientation will not be exactly same for all the devices. It is completely hardware dependent, can vary device to device. You can't fix it, only one option you have just allow user to set the rotation once your application launched at first time get the base rotation angle and save it in to the settings and then give your functionality afterwards.