I have a bitmap that I load from the SD card by allowing the user to choose a picture to display. Once the bitmap is created, I set the Bitmap in an ImageView:
mBitmap = Bitmap.createBitmap(Media.getBitmap(this.getContentResolver(), mPictureUri));
mImageView.setImageBitmap(mBitmap);
This works fine. But, if I change the screen orientation from portrait to landscape, and back again a few times, I always get an OutOfMemory exception.
In onPause, I call mBitmap.recycle(), and then on onResume, I call the above code again to create the bitmap and set the ImageView. Since I'm recycling the image each time, how can I get an OutOfMemory error?
In any case, since that failed, I found a post that said to try using onRetainNonConfigurationInstance() and getLastNonConfigurationInstance(). See post Save cache when rotate device. I changed my code to work this way, and I still get an error. Changing the code, I had the call to getLastNonConfigurationInstance() in onCreate(), and removed all code in onPause and onResume.
Can someone tell me what the problem is, or provide some way to simply load an image, and then be able to pause and resume the Activity without running out of memory? Thanks.
Try reducing the size of your bitmap. How big is it? Use BitmapFactory.options.
Also instead of using "this instance". See the article about memory leaks:
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
The memory leaks could be due to holding on to the context instance which in turn contains references to all the objects from the Activity before it was destroyed.
The article explains it better.
You should reduce size of the images because there is 16MB per app, if you have large bitmaps being recreated, they could accumulate to 16MB before they are garbage collected or if there are memory leaks.
Related
As title says, I have about eight acitivies with layout full of high-res images. On weaker android devicies with low RAM memory it opens each activity alone, but when I try open another, it crashes. But when I restart app and open that activity, it works. What should I do to clean apps memory from these images from first activity to be able to open another activity? Does onDestroy() clean it?
If it like resource images in xml layout, you don't need to clean up them, Android will do it for you. But if you use some big bitmaps objects.
Bitmpap bmp; // not null
bmp.recycle();
bmp = null;
final boolean bmpIsRecycled = bmp.isRecycled()
// Returns true if this bitmap has been recycled.
Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as “dead”, meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. 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.
And actually when your app crashes, what error log do you have? Maybe it's not related with memory leak?
I have an object that overwrites the Application object. In it, I have a member variable which is a LongSparseArray where the key is some identifier of type long and the value is an object with 2 member variables: a Bitmap and a long which is used as a timestamp.
This is my global image cache. Occasionally, a function is ran that looks at the timestamps and ages things that are over an hour old.
By "age" I mean that it removes that entire entry from the LongSparseArray.
Here is my question:
Suppose I have an Activity with a ListView. Each row in the ListView has an ImageView that is populated with an image from the cache.
Bitmap image = ((MyApp)getApplicationContext()).getImage(id);
holder.imgImage.setImageBitmap(image);
Now, suppose the user clicks some button which takes them to a new Activity. While on this new Activity, the image previously assigned to a row in the ListView in the previous Activity ages.
So, to recap, that Bitmap key/value entry now no longer exists in the global LongSparseArray.
Is that Bitmap really able to be reclaimed by Java? Isn't it still being referred to by the ImageView in the ListView of the previous Activity? Assuming, of course, that Android hasn't reclaimed the memory used by that Activity.
The reason I'm asking about this is my previous aging function would also call .Recycle() on the Bitmap. In this scenario, when the user hit the back button and returned to the previous Activity which was using that Bitmap, the application would crash, presumably because that Bitmap was not only missing from the cache, but also from memory. So I just removed the .Recycle() call.
By the way, once the Bitmap is removed from the cache, and an object with that id shows up on screen again, the application will download the Bitmap again and place it in the cache. If the previous one stayed in memory, you could see how this would present a problem.
Also, does anyone have any ideas for a more effective solution?
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
There are 2 Activities which use this image caching. One is a search screen that displays a list of items (and their images) after the user performs a search. The other is a list of those items the user has then selected to keep.
Issue: Once recycle() method is called on a bitmap, the bitmap should never be used again. If an attempt is made to draw the bitmap, then an exception will be thrown. From docs:
You should use recycle() only when you are sure that the bitmap is no
longer being used. If you call recycle() and later attempt to draw the
bitmap, you will get the error: "Canvas: trying to use a recycled
bitmap".
In this specific case, you have recycled the bitmap, but the ListView item's ImageView has a strong reference to the bitmap. When you return to the Activity, the ListView item attempts to draw the bitmap, hence the exception is thrown.
Bitmap memory management: Prior to Android 2.3.3, the backing pixel data of a bitmap was stored in native memory and bitmap itself in Dalvik memory. Hence to release the native memory, recycle method has to be called.
Here is Bitmap.recycle function definition:
public void recycle() {
if (!mRecycled) {
if (nativeRecycle(mNativeBitmap)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
}
}
Post Android 3.0, the backing pixel data is also stored in Dalvik memory. When the bitmap is no longer required, we need to ensure we don't hold any strong reference to the bitmap, so that it is garbage collected.
Solution: If you are still supporting Android 2.3.3 and lower version, you still need to use recycle to release the bitmap.
You can use reference counting to track whether the bitmap is currently being referenced by the ListView item, so that even it is aged, you don't call recycle on the bitmap.
ListView adapater's getView method is the place where the bitmap is assigned to the ImageView. Here you increment the reference count. You can attach setRecyclerListener to the ListView to know whenever the listview item is put into recycle bin. This is the place you would decrement the reference count of the bitmap. The aging function need to recycle the bitmap only if the reference count is zero.
You can also consider using LruCache for caching, as mentioned in docs.
setDrawingCacheEnabled: By calling this method with true param, the next call to getDrawingCache will draw the view to a bitmap. The bitmap version of view can be rendered on to the screen. Since it is just a bitmap, we cannot interact with it as done with an actual view. Couple of use cases are:
When ListView is being scrolled, the bitmap of the displayed items view is captured and rendered. So that the views being scrolled don't undergo measure and layout pass.
View hierarchy feature in DDMS.
Is that Bitmap really able to be reclaimed by Java? Isn't it still
being referred to by the ImageView in the ListView of the previous
Activity? Assuming, of course, that Android hasn't reclaimed the
memory used by that Activity.
The Bitmap is stilled used in the ListView (a strong reference) so dalvik can't reclaim its memory.
Apparently you can't call recycle on the Bitmap or bad things will happen(app crash, e.g.).
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
If you disable drawing cache, every time your view needs to be redrawn, the onDraw method will be called.I'm not very familiar with ImageView , you can go and read its source for a deep understanding.
(Note: the usage of drawing cache is different when hardware accerleration is enabled/disabled, here I just assume you're using software rendering).
For the solution, you can try the following:
when the Bitmap cache become stale, you remove it from the cache array(and then you app will try to get a new one, I think).
In ListView.getView, you can check whether currently used Bitmap ages. It should be easy because you know the timestamp when you call setImageBitmap the first time and the latest timestamp. If they are not same, you call setImageBitmap again using the new Bitmap and the old one will be reclaimed.
Wish this helps.
Regarding, "Also, does anyone have any ideas for a more effective solution?"
The Picasso library would help solve the problems you are facing http://square.github.io/picasso/
Picasso is "A powerful image downloading and caching library for Android"
"Many common pitfalls of image loading on Android are handled automatically by Picasso:
Handling ImageView recycling and download cancelation in an adapter.
Automatic memory and disk caching."
I have done following things to handle my bitmaps in application:
LruCache for Bitmaps with size of 1/8 of memory
Using BitmapFactory.Options to calculate inSampleSize
Catching OOM when creating Bitmaps, calling evictAll and System.gc() there
Also there are AsyncTask decoding sometimes for Bitmaps
I use BitmapFactory.decodeFile and it looks like VM is not freeing Bitmaps fast enough from memory. I read somewhere that there might be bug using BitmapFactory.decodeFile so I tried to use BitmapFactory.decodeFileDescriptor, but there I randomly get following:
skia --- decoder->decode returned false
So is there something wrong with FileInputStream needed to fix here if I wan't to use BitmapFactory.decodeFileDescriptor or something else to do.
This has taken me too much time and I've read all solutions based on this and how Google advices Bitmap handling and I've come to dead end.
Thanks.
Using large Bitmap always there is a chance to get Out Of Memory Exception..
So to handle go through Android blog
http://developer.android.com/training/displaying-bitmaps/index.html
And always recycle the Bimap
ImageView mImage;
Drawable toRecycle = mImage.getDrawable();
if ( toRecycle != null && toRecycle instanceof BitmapDrawable ) {
if ( ( (BitmapDrawable) mImage.getDrawable() ).getBitmap() != null )
( (BitmapDrawable) mImage.getDrawable() ).getBitmap().recycle();
}
I ended up using SoftRefences with Bitmap here. Now I can see GC freeing my unused Bitmaps all the time when fast scrolling GridView which draws them.
Tested setting my LruCache size full memory size and still didn't get OOM.
Penalty using this method is not that visible, my GridView scrolls pretty smoothly considering it's drawing very custom image.
System.gc() won't help you and doesn't guarantee anything.
If you absolutely sure, that evicted bitmaps are no longer needed and there is no reference to them anywhere (will catch "Can't draw recycled bitmap" exception otherwise) I would suggest you to add an EvictionListener to your LRU cache and call bitmap.recycle() on every evicted value.
Can't remember if default LRU cache provides convenience methods for setting eviction listener, but if not, it's extremely easy to extend it and add required functionality.
P.S. I would advice against WeakReferences since you lose any control of your bitmaps and purpose of LRU. Even if you load 8 bitmaps that nicely fit in your 1/8 of memory, but screen can only display 4 of them at a time (ImageViews holding a strong references to Bitmaps) gc will clear remaining 4 as soon as possible. And I really mean ultra fast. You'll have to reload bitmap for each new row (in case of ListView) you are displaying. And every bitmap that went offscreen will have to be reloaded again.
On Android pre-honeycomb, Bitmaps have freaky memory issues because their data isn't stored in the VM. As a result it isn't tracked or removed by the GC. Instead it is removed when Bitmap.recycle() is called (and that is also done automatically in the Bitmap's finalizer).
This leads to some problems when doing image caching. When a bitmap is due to be evicted, I can't simply call recycle() on it, because I have no idea if anyone else is using it.
My first thought was to do System.gc() just before I load each bitmap. That way, hopefully orphaned Bitmaps will be finalized and the native memory freed. But it doesn't work. Edit: Actually it does sort of work. I had my System.gc() in the wrong place, after moving it, and halving my cache size (to what seems like a ridiculously small 2 MB of uncompressed bitmap data), my app no longer seems to crash (so far)!
My next thought was to implement manual reference counting, by subclassing Bitmap and calling ReferenceCountedBitmap.decrementCount() in all my activities' onDestroy() methods. But I can't because Bitmap is final.
I am now planning a BitmapManager which keeps WeakReference's to the bitmaps, and has methods like:
public void using(Bitmap bm);
public void free(Bitmap bm);
which count the references.
Does anyone have any experience or advice handling this? Before you suggest it, I can't ignore 80% of the market.
Well, I solved this with a bitmap manager, where I save the referencing views. In a map-like structure bitmap -> list of views.
Before I call recycle() on a bitmap, I first set all the references from the views to null (otherwise it will throw bitmap recycled exception).
Manual garbage collection, as you say, doesn't work for bitmaps pre-honeycomb, since they are allocated in the native heap and even with System.gc() you can't make assumptions when this memory will be released.
I am metting an OutOfMemory exception after a while, while inflating an XML View with a bitmap.
The problem occurs after opening many times activities that are using many bitmap but always inflated from the XML.
I check with MAT the Memory.
First question : I don't retain any of my Activity in memory
- If I filter on the name of my Activity, i get Objects = 0, but on some activities
I could see RetainedHeap >= 302. Does this indicate a leak ?
After that, I try to compare my memory in different point of time, to see the objects that have been created. But I can't find any thing significant.
My Question is : If I don't retain activities in memory what could be the leak reason ?
What tools/Method to use to find it ?
Regards,
Olivier
Try setting all references to the bitmaps to null when the Activity is destroyed. This may not answer your questions but it should solve your problem.
If you have big source images then scale them to target size: Strange out of memory issue while loading an image to a Bitmap object
Cache Bitmaps (but not Drawables!!) in a List (or Map) and save them in onRetainNonConfigurationInstance(): http://developer.android.com/resources/articles/faster-screen-orientation-change.html