I have looked all over for a solution to my problem and just can't seem to figure it out. I'm sure it is probably 1 or 2 simple lines and hopefully someone can steer me in the right direction.
In my app, a user can click a button that will open the gallery. Once they select an image, it will display that image in an ImageView within my app. That part is working perfectly fine. Originally, I just had it return a uri from the gallery and I would directly display that with this:
imageView1.setImageURI(myUri);
Well, obviously I am now running into the dreaded "Out Of Memory" error if the user reloads that page several times in a row so I'm having to clean up my code to scale down the image. I have done this by implementing a bitmap class that turns the image into a bitmap and scales it down for me. Now, my ImageView display code looks like this:
imageView1.setImageBitmap(bitmap1);
That part is working fine as well. HERE IS THE PROBLEM:
I convert the uri path to a string and then save that in a SharedPreference. This is so that when the user exits the application and the comes back later, the image that they set automatically displays. I convert the uri like this:
...
selectedImageUri = data.getData();
String selectedImagePath;
selectedImagePath = getPath(selectedImageUri);
...
The old method to retrieve the SharedPreference String, convert it to uri, then display it was working fine. (except for the Out Of Memory error of course) It looked like this:
Uri myUri = Uri.parse(selectedImagePath);
imageView1 = setImageURI(myUri);
"selectedImagePath" is obviously the String that I retrieved from the SharedPreference. Again, this worked fine but would throw the error if reloaded too many times.
The part that IS NOT WORKING now is when I try to implement the new Bitmap conversion so that I can scale the bitmap and not get the memory error. Here is the code for that:
Uri myUri = Uri.parse(selectedImagePath)
Bitmap bitmap = getThumbnail(myUri);
imageView1.setImageBitmap(bitmap);
This displays nothing. The original image choosing displays the image fine but when I return to this screen and it tries to parse the string from the SharedPreference and then convert it to the bitmap, nothing ever displays. The code for the "getThumbnail" method was taken directly from THIS POST --->
How to get Bitmap from an Uri?
It is the 3rd answer down.
Anyone have any ideas? Sorry for the super long post but I'd rather over explain my problem than not give enough info. Sorry if this was answered somewhere else. I've been hunting through other questions for hours and have just not found anything that solves my problem.
Thanks.
I figured it out so here is what I did for anyone else having this unique problem. After the image is chosen from the gallery and it returns the intent, I got the data from that intent via this code:
selectedImageUri = data.getData();
Then I got the path from that via this:
selectedImagePath = getPath(selectedImageUri);
Which made a call to this "getPath" method:
public String getPath(Uri uri)
{
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
return cursor.getString(idx);
}
Then I saved "selectedImagePath" as a SharedPreference string.
Later, to retrieve that string and convert it back to showing an image, I first retrieved the SharedPreference string and converted it back to "selectedImagePath". Then, I set it in the ImageView like this:
targetImage = (ImageView)findViewById(R.id.imageView1);
targgetImage.setImageBitmap(decodeSampledBitmapFromResource(selectedImagePath, 200, 200));
which made a call to the following methods:
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 = 2;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(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);
}
It's a heck of a lot of code to do a fairly simple task but it works so I'm happy and moving on. Hopefully this will help someone else who needs to accomplish the same thing.
Related
I got this listener:
#Override
public void onClick(View v) {
showDialog();
ImageButton currentButton = (ImageButton)v;
Bitmap bmp = BitmapFactory.decodeFile(mCurrentPhotoPath);
currentButton.setImageBitmap(bmp);
}
Image is successfully taken in showDialog() but after this it wont set to imageButton. It goes white. The path generated is:
file:/storage........../filename.jpg amd its saved in mCurrentPhotoPath
I tried setting image from drawable after shot is made just to try it and it works. For some reason i cant set the image to the ImageButton.Do i have to resize it(photo is made full sized)?
Following THIS tutorial i did this:
#Override
public void onClick(View v) {
showDialog((ImageButton)v);
setPic((ImageButton)v);
}
private void setPic(ImageButton mImageView) {
if (shotFired){
int targetW = mImageView.getWidth();
int targetH = mImageView.getHeight();
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath,bmOptions);
mImageView.setImageBitmap(bitmap);
mImageView.setTag(mCurrentPhotoPath);
shotFired=false;
}
Seems if i calll setPic() without making a new shot and just call the old one ive made it works. Must be something in timing...I need to get this thing working.
If you are using camera for taking the picture to set it to ImageView, then you need to set the image in onActivityResult() after the image is captured using camera.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
//set the image to imageView
} else if (resultCode == Activity.RESULT_CANCELED) {
//image was not taken
//show some error message
}
}
i believe you problem is because of outOfMEmory problem that normaly happens when loading a bitmap into the memory.
the best practice to load bitmaps in android is to find the bitmap dimensions before you load it. then loading a suitable size of the bitmap depend on your device memory size and screen size.
i highly recommend you to read this great tutorial in develope.android website. this will help you to get a complete sens about how bitmaps works and whats the best practice to load them also source code is included. please go through all these tutorials.
https://developer.android.com/training/displaying-bitmaps/index.html
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
https://developer.android.com/training/displaying-bitmaps/process-bitmap.html
https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
https://developer.android.com/training/displaying-bitmaps/manage-memory.html
https://developer.android.com/training/displaying-bitmaps/display-bitmap.html
i suggest you to read these links but if you nor intersted you can use libraries like Glide or Piccasso for loading your bitmap. they will help you to load without an OutOfMemoty Error.
For an app I'm developing, I am trying to populate a GridView with a lot of images. To avoid OutOfMemoryExceptions, I check the amount of available memory and when a certain threshold is reached, I try to free up memory like so:
private void freeUpMemory() {
// Clear ImageViews up to current position
for (int i = 0; i < mCurrentPosition; i++) {
RelativeLayout gridViewElement = (RelativeLayout) mGridView.getChildAt(i);
if (gridViewElement != null) {
ImageView imageView = (ImageView) gridViewElement.findViewById(R.id.image);
imageView.getDrawable().setCallback(null);
imageView = null;
}
}
}
I noticed that this does not actually free up memory. What I don't know is why. Am I missing something?
When your ImageAdapter gets the "getView()" callback with convertView not null, it is telling you that this view previously supplied by the ImageAdapter is no longer visible on the screen. That's a good time to recover the resources used by the view. Something along the lines of:
ImageView iv = (ImageView)convertView.findViewById(R.id.image_view_in_grid_item);
iv.setDrawable(null);
should remove the reference to the Drawable that is stored in the ImageView. If there are no other references in your code to that Drawable it should be available for garbage collection.
Better yet, if you have another image to be displayed.
iv.setDrawable(newImage);
Then returning convertView as the new view to be used by the grid
will replace the old Drawable with a new one, removing the reference and potentially garbage collecting the image.
You should have a look to the BitmapFactory.Options class of Android. It offers many controls on the Bitmap, and two are very interesting when dealing with a lot of images.
The best solution, I think, is to set inSampleSize to a value like 2 or 4. This will reduce the quality of the image, but will save a lot of memory. Try different values until you find a good ratio.
Sample from Android doc (http://developer.android.com/training/displaying-bitmaps/load-bitmap.html) :
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);
}
There's also inPurgeable, allowing the system to use space from existing Bitmap, but you must be careful as it can leads to crash or invalid bitmaps.
I know this must be one of the most asked things at SO, but none of the other answers gave me a solution. But from reading the other answers, looks like I'll need to redesign the way the App is working.
It's like this, we have a ScrollView, which will inflate some views. A ListView can't be used in this situation, because to behave the way we want it would require extending the ListView, and this is something we don't want to do (even though this seems to be our only solution to our current way of showing items, because of this OOM exception). The list can have a lot of columns per row, and the bigger the screen, more columns it will have.
Each inflated View has a layout displaying some info from the database, including a picture. This picture is stored through a byte array. It's any picture taken with the device camera. Currently every photo (byte array) is taking 800kb to 1mb, which seems a lot to me. Now the list have 30+ items. I took photos until the OOM happened, and it happened when I took a total of 6 photos (occasionally 7). That would be 8mb-9mb of data. Everytime I go to other Activity, and go back to the Activity the ScrollView is in, the list needs to be repopulated.
This is the snippet of the PopulateList method:
if (item.getImg() != null) {
if (App.debug) {
Log.d(TAG, "Setting bmp.");
}
Bitmap bmp = App.byteArrayToBmp(item.getImg());
imgV.setImageBitmap(bmp);
}
Every inflated View will open an 'Advanced Dialog', which will contain other info. Maybe the Image could be there instead on the list (meaning that there would be only 1 bitmap, as every inflated View shares the same advanced dialog). Or I could extend the ListView and benefit from it recycling method (It's not a good solution as I though it would be considering more than 6 items can be at the screen). Another thing that bothers me is every picture having 800kb. Seems like a lot for a 128x128.
This is the setup for the size:
cameraParams.setPictureSize(App.pxToDpi(128), App.pxToDpi(128));
cameraParams.setPictureFormat(PixelFormat.JPEG);
camera.setParameters(cameraParams);
public static int pxToDpi(int px) {
final int scale = app.getApplicationContext().getResources().getDisplayMetrics().densityDpi;
int pixels = (int) px * (scale / 160);
return pixels;
}
So, do you think there is a solution to my issue keeping the current model of my App, or will I need to reformulate?
EDIT: The bitmap method:
public static Bitmap byteArrayToBmp(byte[] byteArray) {
Bitmap img = null;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
img = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
return img;
}
You might want to look at the Official Android Training docs, they've just been updated:
Check out Displaying Bitmaps Efficiently with the lesson: Loading Large Bitmaps Efficiently that goes over this.
Basically you can decode the image using sampleSize to decode it to the width and height you want:
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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
Explained in much great detail in the links above
I have an android App with plenty of animations.
When I programmatically create animations (using AnimationDrawable) the non-java object (as appears in DDMS Heap tab) grows with every new animation I load and never shrinks back even after my animations get released.
I have only one reference to each AnimationDrawable object from a wrapper object I wrote and I verified this object gets released by overriding the finalize method and making sure it gets called.
Eventually android stops loading images and prints "out of memory" errors to the log.
The interesting thing is that this happens only in some devices (Motorola Xoom, Sony Experia) and not in others (such as the Galaxy S).
This problem is not specific Honeycomb or pre-Honeycomb as you can see from the device examples I gave.
Some of the things I tried:
Calling recycle on each of the frames after I am done with the current animation but it doesn't seem to help.
Assigning null to the AnimationDrawble object
Making sure that there are no static variable related to the class holding the reference to the animation drawable
Make sure the problem disappears once I comment out myAnimation.addFrame(...)
This isn't an exact answer, but rather a helpful hint to find where the exact leak is occurring. Perform a heap-dump after you expect your memory to be reclaimed and see why the objects you think should be dead are still alive.
Make sure you get the memory analyzer tool for eclipse. (http://www.eclipse.org/mat/)
There could be two possible reason, first at the time of creating the bitmap and second when you are converting the bitmap into the BitmapDrawable. As i can see from your comment (new BitmapDrawable(currentFrameBitmap) now this method is depreciated better to use BitmapDrawable(getResources(),currentFrameBitmap) Without the Resources reference, the bitmap may not render properly, even when scaled correctly. To load bitmap efficiently you can scale it properly.
public class BitmapDecoderHelper {
private Context context;
public BitmapDecoderHelper(Context context){
this.context = context;
}
public 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;
Log.d("height reqheight width reqwidth", height+"//"+reqHeight+"//"+width+"///"+reqWidth);
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
public Bitmap decodeSampledBitmapFromResource(String filePath,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Log.d("options sample size", options.inSampleSize+"///");
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// out of memory occured easily need to catch and test the things.
return BitmapFactory.decodeFile(filePath, options);
}
public int getPixels(int dimensions){
Resources r = context.getResources();
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dimensions, r.getDisplayMetrics());
return px;
}
public String getFilePath(Uri selectedImage){
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
}
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);