I'm using an LRUCache
to cache bitmaps which are stored on the file system. I built the cache based on the examples here: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
The problem is that I'm seeing OutOfMemory crashes frequently while using the app. I believe that when the LRUCache evicts an image to make room for another one, the memory is not being freed.
I added a call to Bitmap.recycle() when an image is evicted:
// use 1/8 of the available memory for this memory cache
final int cacheSize = 1024 * 1024 * memClass / 8;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) {
oldBitmap.recycle();
oldBitmap = null;
}
};
This fixes the crashes, however it also results in images sometimes not appearing in the app (just a black space where the image should be). Any time that occurs I see this message in my Logcat: Cannot generate texture from bitmap
.
A quick google search reveals that this is happening because the image which is displaying has been recycled.
So what is happening here? Why are recycled images still in the LRUCache if I'm only recycling them after they've been removed? What is the alternative for implementing a cache? The Android docs clearly state that LRUCache is the way to go, but they do not mention the need to recycle bitmaps or how to do so.
RESOLVED:
In case its useful to anyone else, the solution to this problem as suggested by the accepted answer is to NOT do what I did in the code example above (don't recycle the bitmaps in the entryRemoved()
call).
Instead, when you're finished with an ImageView (such as onPause()
in an activity, or when a view is recycled in an adapter) check if the bitmap is still in the cache (I added a isImageInCache()
method to my cache class) and, if it's not, then recycle the bitmap. Otherwise, leave it alone. This fixed my OutOfMemory
exceptions and prevented recycling bitmaps which were still being used.
It won't be, until the
Bitmap
is recycled or garbage-collected.Which is why you should not be recycling there.
Presumably, they are not in the
LRUCache
. They are in anImageView
or something else that is still using theBitmap
.For the sake of argument, let's assume you are using the
Bitmap
objects inImageView
widgets, such as in rows of aListView
.When you are done with a
Bitmap
(e.g., row in aListView
is recycled), you check to see if it is still in the cache. If it is, you leave it alone. If it is not, yourecycle()
it.The cache is simply letting you know which
Bitmap
objects are worth holding onto. The cache has no way of knowing if theBitmap
is still being used somewhere.BTW, if you are on API Level 11+, consider using
inBitmap
.OutOMemoryErrors
are triggered when an allocation cannot be fulfilled. Last I checked, Android does not have a compacting garbage collector, so you can get anOutOfMemoryError
due to fragmentation (want to allocate something bigger than the biggest single available block).Faced the same and thanks to @CommonsWare for the discussion. Posting the full solution here so it helps more people coming here for the same issue. Edits and Comments are welcomed. Cheers
Precisely when your Bitmap is neither in cache and nor getting referenced from any ImageView.
To maintain the reference count of bitmap we have to extend the BitmapDrawable class and add reference attributes to them.
This android sample has the answer to it exactly. DisplayingBitmaps.zip
We will get to the detail and code below.
Not exactly.
In entryRemoved delegate check whether Bitmap is still referenced from any ImageView. If not. Recycle it there itself.
And vice versa which is mentioned in the accepted answer that when view is about to get reused or getting dumped check its bitmap (previous bitmap if view is getting reused) is in the cache. If it is there leave it alone else recycle it.
The key here is we need to make check at both the places whether we can recycle bitmap or not.
I will explain my specific case where i am using LruCache to hold bitmaps for me. And displaying them in ListView. And calling recycle on bitmaps when there are no longer in use.
RecyclingBitmapDrawable.java and RecyclingImageView.java of the sample mentioned above are the core pieces we need here. They are handling things beautifully. Their setIsCached and setIsDisplayed methods are doing what we need.
Code can be found in the sample link mentioned above. But also posting the full code of file in the bottom of answer in case in future the link goes down or changed. Did a small modification of overriding setImageResource also to check state of previous bitmap.
--- Here goes the code for you ---
So your LruCache manager should look something like this.
LruCacheManager.java
And your getView() of ListView/GridView adapter should look normal like usual. As when you are setting a new image on ImageView using setImageDrawable method. Its internally checking the reference count on previous bitmap and will call recycle on it internally if not in lrucache.
Here is your RecyclingImageView.java
Here is your RecyclingBitmapDrawable.java