In my application I have photos, videos, etc.. In the case of images, I have done scaling, but sometimes I get an OutOfMemoryError. How can I handle the error efficiently?
Check that the image size is smaller than the available memory before attempting to load it. So the most efficient way to handle OutOfMemoryException is to architecture your application in such a way that it never attempts to load lots of data into memory in order to avoid the exception.
There is a method in Activity which is called when the device is coming low of memory, but this can only be used to trigger cache files cleaning. This does not mean that your application process is coming out of memory.
You could also add a try catch block to catch Error or OutOfMemoryError, but this would be too late.
Handling large numbers of Bitmaps or large Bitmaps is really difficult in android applications. You'll find some tips on this subject in this article from Romain Guy.
You can also take care of loading bitmaps directly to the resolution you need by specifying a sample size in the BitmapFactory.options you provide to BitmapFactory.decode*() methods.
When dealing with OutOfMemory errors related to bitmap manipulation, checking the size of the decoded bitmap is the best and as far I know only option. Code follows:
public static BitmapFactory.Options getBitmapOptionsWithoutDecoding(String url){
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(url, opts);
return opts;
}
public static int getBitmapSizeWithoutDecoding(String url){
BitmapFactory.Options opts = getBitmapOptionsWithoutDecoding(url);
return opts.outHeight*opts.outWidth*32/(1024*1024*8);
}
//ref:http://stackoverflow.com/questions/6073744/android-how-to-check-how-much-memory-is-remaining
public static double availableMemoryMB(){
double max = Runtime.getRuntime().maxMemory()/1024;
Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memoryInfo);
return (max - memoryInfo.getTotalPss())/1024;
}
public static final long SAFETY_MEMORY_BUFFER = 10;//MB
public static boolean canBitmapFitInMemory(String path){
long size = getBitmapSizeWithoutDecoding(path);
Log.d(TAG, "image MB:"+size);
return size <= availableMemoryMB() - SAFETY_MEMORY_BUFFER;
}
ref: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
I have started to try this routine which loads a jpeg into an ImageView and checks for Out of Memory and re-scales until it fits.
static public boolean tryJpegRead(ImageView imageView, File fn){
if (!fn.exists()){
Log.d("ANDRO_ASYNC",String.format("missing file %s",fn.getAbsolutePath()));
return false;
}
BitmapFactory.Options o = new BitmapFactory.Options();
for (int i = 1; i<10; i++){
o.inSampleSize = i;
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fn.getAbsolutePath(), o);
int h = o.outHeight;
int w = o.outWidth;
Log.d("ANDRO_ASYNC",String.format("going in h=%d w=%d resample = %d",h,w,o.inSampleSize));
o.inJustDecodeBounds = false;
try{
imageView.setImageBitmap(
Bitmap.createScaledBitmap(
BitmapFactory.decodeFile(fn.getAbsolutePath(), o),
w,
h,
true));
return true; // only happens when there is no error
}catch(OutOfMemoryError E){
Log.d("ANDRO_ASYNC",String.format("catch Out Of Memory error"));
// E.printStackTrace();
System.gc();
}
}
return false;
}
In case you have big images like backgrounds or similar, a easy way to prevent Out Of Memory , is to move images from drawable-xhdpi to drawable-nodpi , but take care, this will load the bitmap without any modification.
The good way should be used BitmapFactory.options to fit your necessity
Use android:allowBackup="true", android:hardwareAccelerated="false" and android:largeHeap="true" for solve this
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher"
android:hardwareAccelerated="false"
android:largeHeap="true"
android:theme="#style/AppTheme">
Related
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 am developing a game and I was working with the graphics, I run into some force closes. So my question is : How can we make images to consume less memory in android?
I´ll explain my game, it´s a logic game with a few small images and a background. I´m testing the app in a galaxy note, 1 GB of RAM, and I thought it could take the high resolution but if I use big image for the background, it force closes after going to the pause layout and back.
So I have lowered the graphics and done with no force closes. Anyway is there some way to avoid this memory issue? I´m setting the images directly on the xml is that wrong?
Solved
I have decided to go with this method, thanks to Durairaj Packirisamy for the answer
Here is my code:
Bitmap unscaledimgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on);
ImageView switch1 = (ImageView) findViewById(R.id.switch1);
int viewheight = screenheight / 10;
int imgheight = unscaledimgswitch.getHeight();
switch1.getLayoutParams().height = viewheight;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = ScaledFactor.getScaleFactor(imgheight ,viewheight); // de esta forma cargo la imagen del tamaño exacto necesario
options.inJustDecodeBounds = false;
imgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on, options);
switch1.setImageBitmap(imgswitch);
And in an other class
static int getScaleFactor(int imgheight, int viewheight) {
int result;
result = imgheight / viewheight ;
return result;
}
You need to use a proper BitmapFactory.options.inSampleSize value, If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Also have a look here:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Days, I've spent working on this. Weeks, perhaps. Literally. :(
So I've got an image on an SD card that more than likely came out of the built-in camera. I want to take that image and downsample it to an arbitrary size (but always smaller and never larger). My code uses standard Android Bitmap methods to decode, resize, recompress, and save the image. Everything works fine as long as the final image is smaller than 3MP or so. If the image is larger, or if I try to do several of these at once, the application crashes with an OutOfMemoryError. I know why that's happening, and I know it's happening for a perfectly legitimate reason, I just want it to not happen anymore.
Look, I'm not trying to launch a rocket here. All I want to do is resize a camera image and dump it to an OutputStream or even a temporary file. Surely someone out there must have done such a thing. I don't need you to write my code for me, and I don't need my hand held. But between my various programming abortions and days of obsessed Googling, I don't even know which direction to head in. Roughly speaking, does anyone know how to decode a JPEG, downsample it, re-compress it in JPEG, and send it out on an OutputStream without allocating a massive amount of memory?
Ok I know it's a little bit late but, I had this problem and I found solution. It is actually easy and I am sure it supports back to api 10(I have no idea about before 10). I tried this with my phone. It is a samsung galaxy s2 with an 8mp camera and the code perfectly resized camera images to the 168x168 as well as images i found on web. I checked the images by using file manager too. I never tried resizing images to bigger resoulation.
private Bitmap resize(Bitmap bp, int witdh, int height){
return Bitmap.createScaledBitmap(bp, width, height, false);
}
you can save it like this
private void saveBitmap(Bitmap bp) throws FileNotFoundException{
String state = Environment.getExternalStorageState();
File folder;
//if there is memory card available code choose that
if (Environment.MEDIA_MOUNTED.equals(state)) {
folder=Environment.getExternalStorageDirectory();
}else{
folder=Environment.getDataDirectory();
}
folder=new File(folder, "/aaaa");
if(!folder.exists()){
folder.mkdir();
}
File file=new File(folder, (int)(Math.random()*10000)+".jpg");
FileOutputStream os=new FileOutputStream(file);
bp.compress(Bitmap.CompressFormat.JPEG, 90, os);
}
thanks to this link
The following code is from my previous project. Key point is "options.inSampleSize".
public static Bitmap makeBitmap(String fn, int minSideLength, int maxNumOfPixels) {
BitmapFactory.Options options;
try {
options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fn, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, minSideLength, maxNumOfPixels);
options.inJustDecodeBounds = false;
//Log.e(LOG_TAG, "sample size=" + options.inSampleSize);
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(fn, options);
} catch (OutOfMemoryError ex) {
Log.e(LOG_TAG, "Got oom exception ", ex);
return null;
}
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 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 == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}
Memory Issues
So I am writing an app that should be able to page through detail views that have one large 640 x 480 image on top and 3 images that are part of a gallery that is being lazy loaded. Following Google design guidelines this is what they suggest doing. I can page through maybe 12 - 13 fragments before it crashes because of being out of memory. I think that there are a couple of culprits in this problem.
1.) I am using the FragmentStatePager. Shouldn't this be destroying the fragments that are not being viewed when memory becomes an issue? This is not happening. I thought it was automatic. What do I have to do to make this happen? Could it have something to do with how I have my Fragment implemented? I do all of my Activity config in onCreateView. For the sake of thoroughness I've included the source for this. Plain Vanilla here:
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return InventoryDetailFragment.newInstance(position);
}
}
2.) I have a method that is trying to figure out the size of the image that needs to be downloaded without placing it in memory. Then compresses the image while downloading it to the required size. This is not successfully implemented. But I'm not sure what is going wrong.
private Bitmap downloadBitmap(String url, int width, int height) {
Bitmap bitmap = null;
int scale = 1;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, options);
if (options.outHeight > height || options.outWidth > width) {
scale = (int) Math.max(((options.outHeight)/ height), ((options.outWidth)/ width)); }
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
bitmap = BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, o2);
cache.put(url, new SoftReference<Bitmap>(bitmap));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Error e){
Log.d("TEST", "Garbage Collector called!");
System.gc();
}
return bitmap;
}
I have tried everything that I know how to do but it's beyond my meager grasp of Android/Java. Please help! Thanks!
There are a few things that you need to change:
This is a horrible idea: BitmapFactory.decodeStream((InputStream)new URL (url).getContent(), null, options); You're getting the image from the web each time this is executed (so twice in the code you posted). Instead, you need to download the image and cache it locally.
Add logic to your fragments to call recycle() on the bitmaps as soon as the fragment is detached. Add logic to always reload the image (from the cache) whenever the fragment is attached.
Lastly, your inSampleSize calculation is wrong. inSampleSize should be a value that's a power of two, e.g. 1,2,4,8. You can use logarithms or simple binary logic to get the right one, this is what I use, which will always downsample using at least 2 (only call this if you know that the image is too big):
-
int ratio = (int) Math.max((height/options.outHeight), ( width/options.outWidth); //notice that they're flipped
for (int powerOfTwo = 64; powerOfTwo >=2; powerOfTwo = powerOfTwo >> 1 ) { //find the biggest power of two that represents the ratio
if ((ratio & powerOfTwo) > 0) {
return powerOfTwo;
}
}
if you realize your graphics with opengl, this would not counted to memory.
Another ooption is to use
android:largeHeap="true"
in the manifest. Could be working.
did you use ddvm to search for memory leaks?
http://www.youtube.com/watch?v=_CruQY55HOk
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);