I have a bitmap cache in my Android app where the bitmaps are referenced via SoftReference. However the bitmaps are nulled too early. There can be 20 bitmaps max in the cache, if I load more, GC starts to null the SoftReferences. Without SoftReferences, I can have about 110 bitmaps in cache. Isn't SoftReference supposed to null just before OutOfMemoryError would happen?
No, the implication works the other way around:
It is guaranteed that the Java runtime will nullify SoftReferences, if any, before throwing a OutOfMemoryError. However, it does not guarantee that the SoftReference will get nullified only under this condition.
In my case, Hashmap(bitmap cache) got nulls if I got back from the other Activity.
So, I fixed my code like below pseudo-code.
void displayImageFunction() {
if(mImageCacheMap.containsKey(key)) {
Bitmap bitmapToShow = mImageCacheMap.get(url).get();
if(bitmapToShow != null) {
//> Image was cached well
//> Set "bitmapToShow" to UI
return;
}
}
//> Read from file DB(or Web) and...
Bitmap bmp = getBitmap(imageToLoad.url);
//> put it to Hashmap again...
mImageCacheMap.put(key, new SoftReference<Bitmap>(bmp));
//> Set "bmp" to UI
}
I think we should prefare for nulls.
Because, we cannot know when GC collects garbage. :)
Related
I have some code which is loading an image into an OpenGL texture. In the process, I end up loading 3 bitmaps, since I need to load the original bitmap (sized appropriately for the display) and reorient the bitmap based on EXIF data. I'm very quickly calling .recycle() on each bitmap, but I'm noticing that my memory doesn't seem to change.
Here's what the Memory Monitor shows:
As you can see, after loading the image I'm using about 60MB of memory. When I rotate the device that drops off a bit then comes back up. That leads me to think there is no leak, since the memory never goes above that.
When I click the GC button in the memory analyzer, my memory footprint drops dramatically to around 8 MB. This makes sense as the three bitmaps created during the process were recycled, so can be garbage collected. Then you can see that when I rotate again and the activity is rebuilt, the memory jumps right back up.
Here's my code to show you why so many bitmaps are created and when they're recycled.
void layoutImage() {
...
Bitmap bitmap = loadOrientedConstrainedBitmapWithBackouts(...);
imageTexture = new GLTexture(bitmap);
bitmap.recycle(); // recycle bitmap 2
}
Bitmap loadOrientedConstrainedBitmapWithBackouts(Context context, Uri uri, int maxSize) {
...
Bitmap bitmap = loadBitmapWithBackouts(context, uri, sampleSize); // create bitmap 1
...
Bitmap out = orientBitmap(bitmap, orientation); // create bitmap 2
bitmap.recycle(); // recycle bitmap 1
return out;
}
Bitmap orientBitmap(Bitmap source, int orientation) {
...
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight, matrix, true); // create bitmap 3
}
I'm not really sure that this is a problem, so to speak, since the memory isn't climbing (so no leak), but I'm curious when it stays so high. Since forcing a garbage collection clears it just fine, should I assume that if the system needs that memory it will be collected on the next GC pass? It's been running the whole time I've been writing this and is still sitting comfortably at 60 MB.
Question 1: Can I just trust that the garbage collector will take that memory back if needed?
Also, if we're supposed to be so judiciously recycling our bitmaps, why do so many of the Bitmap methods say things like "The new bitmap may be the same object as source, or a copy may have been made." Do I really have to check the equality every time I use those methods to recycle the bitmap if it's a different object?
Question 2: When using Bitmap creation methods, that may or may not return the same bitmap or a copy, do I need to check source and output equality to recycle the source if it's a copy?
Edit:
I have tried analyzing this with MAT, using a heap dump at peak usage (should be 60 MB), but it only reports 18.2 MB of usage and nothing unusual looking. Could they be reading things differently?
Question 1: Can I just trust that the garbage collector will take that memory back if needed?
Yes. If the incoming references are cleared, the garbage collector will take the memory when it is needed (typically for a new allocation). Calling recycle() doesn't help this process along or make it happen any faster.
The recycle() method exists because Bitmap objects were not counted against the heap until Android 3.0; so the method was helpful to assist the GC since it didn't otherwise have a record of that memory counted against its heap. In 3.0+, the memory is tracked against the heap so this extra bookkeeping isn't necessary anymore.
Question 2: When using Bitmap creation methods, that may or may not return the same bitmap or a copy, do I need to check source and output equality to recycle the source if it's a copy?
The createBitmap() method will return the same object if:
The source is immutable
x and y are both zero
width and height match the source width and height
No transformation matrices have been applied
Since it looks like you are passing in a transformation matrix, you will always get a copy unless the matrix is identity for some reason. But again, no real need to recycle() unless you are still supporting 2.x versions.
I am trying to create cached image system for Android but the memory consumption just grows and grows. I looked through Android website for some ideas, but the issue just doesn't want to disappear.
Below is my code of getting the image from SD card, setting it and later destroying.
What am I doing wrong?
WeakReference<Bitmap> newImageRef;
public void setImageFromFile(File source){
if(source.exists()){
Bitmap newImage = BitmapFactory.decodeFile(source.getAbsolutePath());
newImageRef = new WeakReference<Bitmap>(newImage);
if(newImage != null){
this.setImageBitmap(newImage);
}
}
}
#Override
protected void onDetachedFromWindow() {
Bitmap newImage = newImageRef.get();
if (newImage != null) {
newImage.recycle();
newImage = null;
}
Drawable drawable = getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null){
bitmap.recycle();
}
}
this.setImageResource(0);
newImage = null;
newImageRef = null;
System.gc();
super.onDetachedFromWindow();
}
If you are using Android version >3.0 you dont have to call recycle()as the gc will clean up bitmaps on its own eventually as long as there are no references to it. So it is safe to remove recycle calls. They do nothing much here.
The code which you posted looks neat but are you sure there the leak is not happening somewhere else. Use Android Memory Analyzer tool to see where the leak is happening and then post the info.
Good luck.
Try to use Drawable.setCallback(null);. In Android 3.0 or newer, you don't even need to recycle because of more automatic memory management or garbage collection than in earlier versions. See also this. It has good information about bitmap memory management in Android.
As of this code it's hard to check if there is a detailed bug as this seems to cleary be a simplifyed version of the "full cache". At least the few lines you provided seem to look ok.
The main issue is the GC seems to be a little strange when handling Bitmaps. If you just remove the hard references, it will sometimes hang onto the Bitmaps for a little while longer, perhaps because of the way Bitmap objects are allocated. As said before recycling is not necessary on Android 3+. So if you are adding a big amount of Bitmaps, it might take some time until this memory is free again. Or the memory leak might be in anothe part of your code. For sophisticated problems like that its wise to check already proven solutions, before re-implementing one.
This brings me to the second issue: the use of weak refrences. This might not target the main problem, but is generally not a good pattern to use for image caches in Android 2.3+ as written by android doc:
Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
The way to go now is to use LRU Caches, which is explained in detail in the link provided about caching.
According to Android Developers site, the Bitmap.recycle() method definition is:
Free the native object associated with this bitmap, and clear the reference to the pixel data
I've developed some applications which are creating / decoding a lot of bitmaps, and put the result bitmap objects to ImageViews. Sometimes I've got the famous exceptions such as:
bitmap size excceded vm budget
and
out of memory error
Also I'm sure I don't have any memory leaks that can cause that.
After a lot of searches, I discoverd the "recycle" method, and used it to free the bitmap's native memory when no longer needed. It seems like it helped a lot.
I'm asking if that's something I'm supposed to do on this situation, because
I know the system is doing this anyway without calling it explicitly (is it? maybe I'm wrong).
Should I use this method in situations like this?
In what situations should I use this method?
Should I use this method at all?
thanks in advance.
UPDATE:
google posted this guide recently, which says:
On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle() method allows an app to reclaim memory as soon as possible.
in what situations should I use this method?
The Bitmaps are GC'ed by GC whenever it decides.But in some situations it may get delayed.
And always remember thumb rule in java (Maybe it applies to othe P.L also).The speed of recycling objects by GC may not be same as speed of creating objects.So sometimes the GC is slow to in recycling.
so recycle() means If you want to free memory ASAP you should call recycle()
should I use this method at all??
This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.But if you are facing the issues like bitmap size exceeded vm budget or out of memory error then you need to use this.
I do use it in operations where i know that bitmap is not going to be used anymore.
public static Bitmap getMaskedContactImage (Context context, Bitmap contactImageBitmap, int maskToBeApplied) {
Bitmap mask = BitmapFactory.decodeResource(context.getResources(), maskToBeApplied);
Bitmap output = Bitmap.createBitmap(mask.getWidth(),mask.getHeight(), Config.ARGB_8888);
final Rect finalRect = new Rect(0, 0, contactImageBitmap.getWidth(), contactImageBitmap.getHeight());
final Rect originRect = new Rect(0, 0, mask.getWidth(), mask.getHeight());
Canvas canvas = new Canvas(output);
Paint xferPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
xferPaint.setColor(Color.BLACK);
xferPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
canvas.drawBitmap(contactImageBitmap, finalRect, originRect, null);
canvas.drawBitmap(mask, originRect, originRect, xferPaint);
contactImageBitmap.recycle();
mask.recycle();
return output;
}
In places like that one, im sure im not going to use the mask or the contactImage.
I found a really good resource for Bitmap processing that can be helpfull Displaying bitmaps.
Regards,
Alex
When one should take care of Bitmap memory management or recycling bitmap in android ?
For example , there are few ways to create a bitmaps in android like following
Bitmap.createBitmap
Bitmap.createScaledBitmap
BitmapFactory
But when android allocate memory for bitmap which must be cleared so that in future application we won't face running out of memory error problem
In Android versions prior to 3.0, the Bitmaps are allocated outside of the VM. Android gets back this memory in Bitmap's finalize() method. You can let Android reclaim the memory faster by calling Bitmap.recycle() instead of waiting on the GC to call finalize() on them.
This is only really a problem if you're creating and discarding a lot of the Bitmaps. That is, if you are allocating memory faster then the GC can clean up the garbage left behind, at which point you get an OutOfMemoryError.
In android 3.0 and later, the Bitmap memory is allocated inside of the VM, so Bitmap memory can be reclaimed without having to call finalize() on them.
you recycle the bitmap whenever you don't need it. for example
#Override
protected void onPause(){
super.onPause();
if(bitmap !=null){
bitmap.recycle();
bitmap = null;
}
}
#Override
protected void onResume() {
super.onResume();
if(bitmap !=null){
bitmap.recycle();
bitmap = null;
}
}
P.S bitmaps memory are acting different from each devices, what i mean is
Growing Heap
is different from device to another
for example Bitmap size can exceed VM budget(Growing Heap) up to 240MB on the S4 tested and confirmed by personal testing and it doesn't cause OutOfMemoryError but in some other devices if the bitmap size exceed (Growing Heap) up to 16MB it may cause OutOfMemoryError its quite different from device to another because some devices has large heap while some not. and trust me dealing with Growing Heap is not easy task.
additional advice is to use android:largeHeap="true" in your application tag inside of the manifest.
`
I developed an application that uses lots of images on Android.
The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.
There is no animation, no special effects or anything that can fill the memory.
Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.
Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.
Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.
So what is the best/correct way to handle many images?
Should I put them in static methods so they are not loaded all the time?
Do I have to clean the layout or the images used in the layout in a special way?
One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.
Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.
First, set the “id” attribute on the parent view of your XML layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/RootView"
>
...
Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().
#Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.RootView));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
This unbindDrawables() method explores the view tree recursively and:
Removes callbacks on all the background drawables
Removes children on every viewgroup
It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.
It's difficult to say why this is without looking at your code. However, this article has some tips that might help:
http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html
In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.
To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:
public final void setMyBitmap(Bitmap bitmap) {
if (this.myBitmap != null) {
this.myBitmap.recycle();
}
this.myBitmap = bitmap;
}
And next you can change myBitmap w/o calling System.gc() like:
setMyBitmap(null);
setMyBitmap(anotherBitmap);
I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.
Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.
This explanation might help:
http://code.google.com/p/android/issues/detail?id=8488#c80
"Fast Tips:
1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.
2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.
3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!
4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "
I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.
P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.
Following points really helped me a lot. There might be other points too, but these are very crucial:
Use application context(instead of activity.this) where ever possible.
Stop and release your threads in onPause() method of activity
Release your views / callbacks in onDestroy() method of activity
I suggest a convenient way to solve this problem.
Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity.
like this:
<activity android:name=".main.MainActivity"
android:label="mainActivity"
android:configChanges="orientation|keyboardHidden|navigation">
</activity>
the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:
As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.
I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.
My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.
I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.
To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.
Note: it's not really necessary to change the background to black.
The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).
Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.
The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.
In my case the code looks like this:
private static BitmapDrawable currentBGDrawable;
if (new File(uriString).exists()) {
if (!uriString.equals(currentBGUri)) {
freeBackground();
bg = BitmapFactory.decodeFile(uriString);
currentBGUri = uriString;
bgDrawable = new BitmapDrawable(bg);
currentBGDrawable = bgDrawable;
} else {
bgDrawable = currentBGDrawable;
}
}
I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.
ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.
/**
* Lightweight cache for Bitmap objects.
*
* There is no thread-safety built into this class.
*
* Note: you may wish to create bitmaps using the application-context, rather than the activity-context.
* I believe the activity-context has a reference to the Activity object.
* So for as long as the bitmap exists, it will have an indirect link to the activity,
* and prevent the garbaage collector from disposing the activity object, leading to memory leaks.
*/
public class BitmapCache {
private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();
private StringBuilder sb = new StringBuilder();
public BitmapCache() {
}
/**
* A Bitmap with the given width and height will be returned.
* It is removed from the cache.
*
* An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.
*
* Note that thread-safety is the caller's responsibility.
*/
public Bitmap get(int width, int height, Bitmap.Config config) {
String key = getKey(width, height, config);
ArrayList<Bitmap> list = getList(key);
int listSize = list.size();
if (listSize>0) {
return list.remove(listSize-1);
} else {
try {
return Bitmap.createBitmap(width, height, config);
} catch (RuntimeException e) {
// TODO: Test appendHockeyApp() works.
App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height);
throw e ;
}
}
}
/**
* Puts a Bitmap object into the cache.
*
* Note that thread-safety is the caller's responsibility.
*/
public void put(Bitmap bitmap) {
if (bitmap==null) return ;
String key = getKey(bitmap);
ArrayList<Bitmap> list = getList(key);
list.add(bitmap);
}
private ArrayList<Bitmap> getList(String key) {
ArrayList<Bitmap> list = hashtable.get(key);
if (list==null) {
list = new ArrayList<Bitmap>();
hashtable.put(key, list);
}
return list;
}
private String getKey(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Config config = bitmap.getConfig();
return getKey(width, height, config);
}
private String getKey(int width, int height, Config config) {
sb.setLength(0);
sb.append(width);
sb.append("x");
sb.append(height);
sb.append(" ");
switch (config) {
case ALPHA_8:
sb.append("ALPHA_8");
break;
case ARGB_4444:
sb.append("ARGB_4444");
break;
case ARGB_8888:
sb.append("ARGB_8888");
break;
case RGB_565:
sb.append("RGB_565");
break;
default:
sb.append("unknown");
break;
}
return sb.toString();
}
}