Proper way to release bitmap memory - android

I'm developing my first Android app. I have a method that creates two bitmaps and returns a third bitmap which is an overlay of the second bitmap on top of the first bitmap. So basically, I don't need the two bitmaps once the third bitmap is created.
I've read some posts and articles about releasing bitmap memory and I'm a bit confused on how to handle it.
Do I have to release the bitmaps myself? If yes, what is the proper way to do so?Are they released when the method is finished? Should I just let the garbage collector release it?
public static Bitmap bitmapResizeOverlay(Context context, Uri selectedImage, int maxWidth,
int maxHeight, #DrawableRes int overlayImageResource) {
Bitmap selectedBitmap = bitmapResize(context, selectedImage, maxWidth, maxHeight);
Bitmap overlayBitmap = BitmapFactory.decodeResource(context.getResources(), overlayImageResource);
return overlayBitmapToBottom (selectedBitmap, overlayBitmap);
}

selectedBitmap.recycle()
method is used always in when you want clear the memory occupied with bitmap.
The down side of not recycling the bit map could be OOM (out of Memory Exception)

Related

Android bitmap recycle: need to set null?

Is it necessary to set a Bitmap to null after recycling it, so that its memory can be freed?
In the following code:
private static Bitmap bmpConcatHV() {
Bitmap bmp = BitmapFactory.decodeResource(resId);
Bitmap bmp2 = concatVertically(bmp, bmp);
bmp.recycle();
bmp = null;
Bitmap bmp3 = concatHorizontally(bmp2, bmp2);
bmp2.recycle();
bmp2 = null;
return bmp3;
}
There are Lint warnings:
The value null assigned to 'bmp' is never used
The value null assigned to 'bmp2' is never used
Both bmp and bmp2 are local variables, so no.
BitmapFactory.decodeResource(resId);
bitmap has two part of information when loaded into memory
1- information about bitmap ==> exists in java used memory
2- information about pixels of bitmap (byte of array) ==>exists in c++ used memory
Bitmap.recycle() is used to free the memory of C++.
Garbage Collection will collection the part of java and the memory
Garbage Collection work when need memory but i need to use this reference now so will use
bmp = null; here i handle the part of java and be safe to make it null

Why do I NOT get an out of memory exception?

I have a high resolution image (2588*1603) in drawable folder. If I use below code (1) to set it for the imageView I do not get OOM exception and the image assigned as expected:
public class MainActivity extends ActionBarActivity{
private ImageView mImageView;
int mImageHeight = 0;
int mImageWidth = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);
mImageView.setScaleType(ScaleType.FIT_CENTER);
BitmapFactory.Options sizeOption = new BitmapFactory.Options();
sizeOption.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.a, sizeOption);
mImageHeight = sizeOption.outHeight;
mImageWidth = sizeOption.outWidth;
mImageView.post(new Runnable() {
#Override
public void run() {
try {
BitmapRegionDecoder bmpDecoder = BitmapRegionDecoder
.newInstance(getResources().openRawResource(R.drawable.a),true);
Rect rect = new Rect(0,0,mImageWidth, mImageHeight);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDensity = getResources().getDisplayMetrics().densityDpi;
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
mImageView.setImageBitmap(bmp);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
Note that rect size is exactly the same as image size.
But If I use other methods like for example 2 or 3 I get OOM.
2) mImageView.setBackgroundResource(R.drawable.a);
3) Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
mImageView.setImageBitmap(bmp);
What is the difference between 1 and 2,3 ?
(I know how to solve OOM, I just want to know the difference)
This is the source of BitmapRegionDecoder#decodeRegion:
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
checkRecycled("decodeRegion called on recycled region decoder");
if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
|| rect.bottom > getHeight())
throw new IllegalArgumentException("rectangle is not inside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, options);
}
As you can see, it simply calls a native method. I do not understand enough C++ to see whether the method scales the bitmap down (according to your inDensity flag).
The other two methods use the same native method (nativeDecodeAsset) to get the bitmap.
Number 2 caches the drawable and thus needs more memory. After lots of operations (checking if the bitmap is already preloaded or cashed and other things), it calls a native method to get the bitmap. Then, it caches the drawable and sets the background image.
Number 3 is pretty straight forward, it calls a native method after a few operations.
Conclusion: For me, it is hard to say which scenario applies here, but it should be one of these two.
Your first attemp scales the bitmap down (the inDensity flag) and thus needs less memory.
All three methods need more or less the same amount of memory, number 2 and 3 just a little bit more. Your image uses ~16MB RAM, which is the maximum heap size on some phones. Number 1 could be under that limit, while the other two are slightly above the threshold.
I suggest you to debug this problem. In your Manifest, set android:largeHeap="true" to get more memory. Then, run your 3 different attemps and log the heap size and the bytes allocated by the bitmap.
long maxMemory = Runtime.getRuntime().maxMemory();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long freeMemory = maxMemory - usedMemory;
long bitmapSize = bmp.getAllocationByteCount();
This will give you a better overview.
Ok, down to core, single difference between 1 and 2,3 is that 1 doesn't support nine patches and purgeables. So most probably a bit of additional memory allocated for NinePatchPeeker to work during decoding is what triggers OOM in 2 and 3 (since they use same backend). In case of 1, it isn't allocated.
Other from that i don't see any other options. If you look at image data decoding, then tiled decoding uses slightly more memory due to image index, so if it was the case, situation would be opposite: 1 will be throwing OOMs and 2,3 is not.
Too many detail of the picture results the out of memory.
summary: 1 use the scaled bitmap; 2,3 load the full detailed drawable(this results the out of memory) then resize and set it to imageview.
1
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
the constructor(InputStream is, boolean isShareable) use the stream , which will not exhaust the memory.
use BitmapFactory.Options and BitmapRegionDecoder will scale down the bitmap.
Refer: BitmapRegionDecoder will draw its requested content into the Bitmap provided, clipping if the output content size (post scaling) is larger than the provided Bitmap. The provided Bitmap's width, height, and Bitmap.Config will not be changed
2,3
Drawable d = mContext.getDrawable(mResource);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
there is no scale option, the whole picture will load to memory
Sorry for English.
Maybe help you.
You are not getting OOM exception because of this
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
It is already given here
public Bitmap.Config inPreferredConfig
Added in API level 1
If this is non-null, the decoder will try to decode into this internal configuration. If it is null, or the request cannot be met, the decoder will try to pick the best matching config based on the system's screen depth, and characteristics of the original image such as if it has per-pixel alpha (requiring a config that also does). Image are loaded with the ARGB_8888 config by default.

Custom GridView causes out of memory error while loading lots of images

I have a custom layout for gridview. Each raw of gridview contains a progressbar, two imageviews and two text views. The image size are thumbnail sizes. While i have to load lots of bitmaps eg:- 500 images , which causes out of memory error. the images are first time loading from Internet and then which is stored in SD card and the next time when you loading the gridview which is loading from SD card. How to overcome this issue. I have found lots of answers to overcome out of memory if the gridview is inflated with single imageview. Please suggest me how to overcome this issue while using custom layout.Let me know if you know any example projects that handle this out of memory error in gridview while inflating custom layout in gridview.
It would be best to create a class that extends Application . This
application class will give you onlowmemory() callback whenever
application goes low memory. on there you can write
public void onLowmemory() {
Runtime.getRuntime().gc(); }
which will invoke system GC method. Upon executing garbage collector
android will garbage all unused objects.
There is another way to solve this problem. In animation you can call
Runtime.getRuntime().gc(); to invoke garbage collector. also in
activity onDestroy() method u can call Runtime.getRuntime().gc();
so your problem will be solved
I am facing the same issue.
The OutOfMemoryError is usually caused by
The first approach is to
BitmapFactory.decodeFile(srcImg);
Since images are transformed into bitmap before displaying, lots of big bitmap usually cause the error.
To overcome this, I added the following function
public static Bitmap decodeWithBounds(String srcImg, int bounds) {
if (bounds > 0){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcImg, options);
if (options.outHeight > bounds || options.outWidth > bounds){
options.inSampleSize = Math.max(options.outHeight/bounds, options.outWidth/bounds);
}
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(srcImg, options);
} else {
return BitmapFactory.decodeFile(srcImg);
}
}
I use this function to decode bitmap, with bounds = grid size.
This solved most problem.
For very low end device, add a try{} catch (OutOfMemoryError e){} ...
try this code set in image for gridview out of memory
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 8;//you set size qulity for image(2,3,,4,5,6,7,8 etc..)
Bitmap preview_bitmap=BitmapFactory.decodeStream(is,null,options);
set this bitmap in your imageview
Solve the out of memory exception issue with this code
Bitmap bitmap = BitmapFactory.decodeStream(is);
if (bitmap != null) {
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, true);
imageView = new ImageView(mContext);
imageView.setImageBitmap(resizedBitmap);
}

android create object and memory

If I create an object and assign it to a variable:
Obj obj1 = null;
obj1 = myFunction(params);
(here myFunction creates a complex object)
And later I reassign the variable:
obj1 = myFunction(otherparams);
Does in that moment a memory leak occur, because I did not destroy the previous object?
Here is the real situation:
Bitmap bmp;
bmp = drawMyBitmap(3);
//... some code
bmp = drawMyBitmap(4);
Will a memory leak happen here?
Of cource, I know that I must call bmp.recycle, but I can't do it, because the real code is the following:
Bitmap bmp;
bmp = drawMyBitmap(3);
imageView.setImageBitmap(bmp);
//... some code
// if I try to do recycle here - I receive java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
// But I need to recreate bitmap every some minutes
bmp = drawMyBitmap(4);
imageView.setImageBitmap(bmp);
So, how can I recycle the bitmap and avoid memory leaks?
As I understand, your problem is just you can't recycle your Bitmap cause it's used.
It's pretty naive, so maybe it's wrong, but do this:
imageView.setImageBitmap(bmp);
//... some code
Bitmap tmp = bmp;
bmp = drawMyBitmap(4);
imageView.setImageBitmap(bmp);
tmp.recycle(); // As it's not anymore referenced by the ImageView, you can recycle the Bitmap safely
I didn't test it. Give feedback.
In the first case, you will release the first object's reference so the garbage collector will destroy it, leaving the second one live on memory because of new reference.
In the second case, if you are setting bitmaps to ImageViews, you can not recycle them because the view will not have the bitmap to draw the image and it will throw to you a bitmap recycled exception, so you are not "leaking" to much memory keeping 2 bitmaps on memory.
Try to use bitmap options to create them to optimise your memory comsuption if you want.
Bitmap bmp;
bmp = drawMyBitmap(3);
imageView.setImageBitmap(bmp);
//... some code
// if I try to do recycle here - I receive java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
// But I need to recreate bitmap every some minutes
Bitmap temp = bmp; //try this
bmp = drawMyBitmap(4);
imageView.setImageBitmap(bmp);
temp.recycle();

how do i come out of OutOfMemoryError: bitmap size exceeds VM budget

I am very new in android,and trying to put SDcard images in grid view by using Bitmap and BitmapFactory.
But it cause the Error like:
ERROR/AndroidRuntime(6137): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:271)
Well ok, apparently this question will get answered the 100th time on SO. Here's the idea:
first off depending on the Android version on the device:
if you're using Android version 2.x and below (any version prior to Honeycomb) the memory taken by your Bitmap instances will NOT be reflected in the amount of free memory you have on the heap as reported by Runtime.getRuntime().xxxMemory() methods. Those instances are placed in memory OUTSIDE the heap. If you want to track down how much memory a Bitmap instance will use you have to manually calculate it as imageWidth*imageHeight*4. This will give you the bytes taken by your image in (off heap) memory. This memory consumption, as well as the on-heap memory consumption must have a total which is below the max memory allocated by Android to your application on a certain device, if not you'll get an OutOfMemory error.
the total memory allocated to a process by Android depends greatly on the device and Android version. This can be anywhere between 16 and 48 Megs. On older devices is 16 or 32. On newer ones is 32 or 48. This you have to individually check on each device you want to target. You can do it also at runtime, and use it as a guide as to how much stuff you can put in memory (maybe you want to downsample the images before loading them on a device that allocates 16 Mb of memory to your app)
On Android Honeycomb (version 3.x.x) and beyond, a Bitmap instance will use on-heap memory. This makes it easier to track down how much free memory you still have after loading images. Also with these versions of Android, the Bitmap instance will be garbage collected (when possible) automatically. On pre-Honeycomb you have to manyally call
Bitmap.recycle();
to free up the memory taken by your bitmap instance.
When using BitmapFactory to decode images (and create Bitmap instances) you can pass-in options such as to only get the width and height of an image, or to downsample it before decoding. This can help you asses how much memory an image will take BEFORE you actually place it in memory. Check out the docs: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
If you feel adventorous you can trick the memory limitation all together, though this doesn't work on all devices: Create an OpenglSurfaceView, and display your images as textures on quads. You can use an orthogonal projection for simplicity (if you only need to give appearance of 2d). Trick here is that you can load an Image in memory as a bitmap, asign it to an OpenGL texture and the clear out that Bitmap instance. The actual image will still be displayeble from the Texture object, and these objects are not limited by the per-process memory limitation.
Do not copy the image in full quality into your app first. Use the Options class to sample down the quality/size a bit:
ContentResolver cr = getContentResolver();
InputStream is = cr.openInputStream(chosenImageUri);
Options optionSample = new BitmapFactory.Options();
optionSample.inSampleSize = 4; // Or 8 for smaller image
Bitmap bitmap = BitmapFactory.decodeStream(is, null, optionSample);
// Bitmap bitmap = BitmapFactory.decodeFile(filePathString, optionSample);
Try using inSampleSize = 8 if you are creating thumbnail bitmaps.
If you find yourself creating several Bitmap objects, each making some changes to the same image, try using bitmap.recycle(). But recycle() can lead to some runtime errors if your app has some reference to the old bitmaps (can be hard to detect), so be careful using it.
Let me know if it helps.
Android is more concerned about memory and the BitmapFactory accepts limited size images only.
I think the following will help you to scale image before use.
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
public class ImageScale
{
/**
* Decodes the path of the image to Bitmap Image.
* #param imagePath : path of the image.
* #return Bitmap image.
*/
public Bitmap decodeImage(String imagePath)
{
Bitmap bitmap=null;
try
{
File file=new File(imagePath);
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(file),null,o);
final int REQUIRED_SIZE=200;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true)
{
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize=scale;
bitmap=BitmapFactory.decodeStream(new FileInputStream(file), null, options);
}
catch(Exception e)
{
bitmap = null;
}
return bitmap;
}
/**
* Resizes the given Bitmap to Given size.
* #param bm : Bitmap to resize.
* #param newHeight : Height to resize.
* #param newWidth : Width to resize.
* #return Resized Bitmap.
*/
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth)
{
Bitmap resizedBitmap = null;
try
{
if(bm!=null)
{
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
}
}
catch(Exception e)
{
resizedBitmap = null;
}
return resizedBitmap;
}
}
To get image path from URI use this function :
private String decodePath(Uri data)
{
Cursor cursor = getContentResolver().query(data, null, null, null, null);
cursor.moveToFirst();
int idx = cursor.getColumnIndex(ImageColumns.DATA);
String fileSrc = cursor.getString(idx);
return fileSrc;
}

Categories

Resources