While trying to implement small in-memory cache of Drawables, I learned that to avoid memory leaks after closing activity I need to unbind those Drawables: set their callback to null.
Because maintaining Drawables cached in each activity would require extra code, I tried to unbind them immediately after setImageDrawable(drawable) and I don't see any consequences so far.
This is code from MyImageView class (extends ImageView):
setImageDrawable(drawable);
d.setCallback(null);
In debugger I can clearly see that before first line callback is null, after first line it is set to this imageView, and after that I set it to null again. It is normally shown after that..
Documentation for setCallback (Drawable.Callback cb) states:
Bind a Drawable.Callback object to this Drawable. Required for clients that want to support animated drawables.
Since I don't need animated drawable, I don't see why I shouldn't do this but it bothers me that in several blogs about memory leakage in Android concerning drawables this is done only after activity is done. Question is, why is callback always automatically set when binding to ImageView?
Are there some border conditions where those drawables with callback set to null will cause a problem? Not displaying or NPE?
You should not cache Drawables -- the Drawable object is very stateful, and intended to be used by one and only one owner.
If you want to implement a cache, you should be caching the drawable's constant state.
The constant state is retrieve with this:
http://developer.android.com/reference/android/graphics/drawable/Drawable.html#getConstantState()
(Note this method can return null; not all Drawables have constant state.)
You can later instantiate new Drawables from a constant state with this:
http://developer.android.com/reference/android/graphics/drawable/Drawable.ConstantState.html#newDrawable(android.content.res.Resources)
Also keep in mind that Resources already maintains a cache of Drawables for you, using this facility, so there is no need for you to implement your own cache for any Drawables you are retrieving from Resources.
And if you are making your own Drawables outside of resources, I would strongly recommend making a cache of the underlying data (such as a bitmap downloaded from the network) then trying to mess with the constant state. (And again, definitely don't cache Drawable objects themselves.)
Related
Several Activitys in my app display images in a ListView where each row of the ListView contains an ImageView.
An example of this would be a search screen where the user searches, gets results, and a picture of each result is shown.
I'm trying to weigh the cost/benefits of implementing a global LruCache vs having each Activity contain its own local LruCache.
Here are my two main problems. Both revolve around the fact that my app is quite large, meaning there are quite a few screens which show these images. Also, my app has the popular side menu way of navigating. Because of this, I could open the menu, tap Activity B, open the menu, tap Activity A, open the menu... etc. and create an Activity stack of ABABABABABABABAB indefinitely.
Global
Won't Activitys with ImageViews using Bitmaps from a global LruCache contain references to these Bitmaps? Suppose the user navigates away from this Activity by clicking some Button. That Activity is now on the Activity stack and still holds references to those Bitmaps. If the LruCache pops a Bitmap off, can that Bitmap really be reclaimed when an ImageView in some Activity on the stack holds a reference to it?
I had previously created my own custom cache. If I called recycle() on a Bitmap and then the user hit the back button to go back to some Activity on the stack that contained an ImageView set to that Bitmap, the app would crash. This is why I believe ImageViews on Activitys on the stack still hold references to Bitmaps.
Local
As I mentioned earlier. My app is quite large, and side menu style of navigation allows the user to create rather large Activity stacks. This would create a lot of LruCaches. And, since you have to declare the size of the LruCache when you initialize it, there wouldn't seem to be any good way of picking a size.
Thoughts? Suggestions?
At this point I think I have to do global, but I don't know how to solve the Activity stack reference problem. I can't imagine this isn't a problem many apps haven't run into. I don't know why I'm not finding information about it.
I'm trying to weigh the cost/benefits of implementing a global
LruCache vs having each Activity contain its own local LruCache.
Global LruCache is the way to move forward, since the same set of bitmaps might be referred in different activity instances. The LruCache can be defined part of Application. If the activity stack can host multiple instances of the same activity (like ABABABAB..), then creating a LruCache locally in that activity will be a bad idea. Very soon Out Of Memory situtation will be reached, as LruCache in each activity instance reserves the defined amount of memory in Dalvik VM. Assume, application memory is 32Mb and you decide LruCache size as 4Mb i.e. 1/8th. Now when we create nearly 7 instances of Activity A, then memory consumption will go to 7*4=28Mb, that itself might trigger OOM.
Won't Activitys with ImageViews using Bitmaps from a global LruCache
contain references to these Bitmaps?
Yes ImageView will also have a strong reference to the bitmap. If the reference is maintained in LruCache, then the reference count will be 2 at that moment.
If the LruCache pops a Bitmap off, can that Bitmap really be reclaimed
when an ImageView in some Activity on the stack holds a reference to
it?
No the bitmap memory can't be reclaimed, as still some ImageView is have a strong reference to it.
At this point I think I have to do global, but I don't know how to
solve the Activity stack reference problem.
LruCache main role is holding strong reference to the bitmap which are more frequently used. So that if there is no strong reference held by any ImageView, the bitmap is prevented from being garbage collected.
Also remember, for Android 2.3.3 and lower versions, you need to implement reference counting mechanism, in order to recycle the bitmaps.
I am still new to Android, and never had to deal with memory management in my previous experience.
In my android application, I have an activity with a TextView, a ListView, and ImageView. I have listeners for the textview and listview, and the code that changes the contents in all three of those views. The contents is stored in the arraylist. The source for the ImageView is stored in form of a String (filenames), and the code that makes the changes looks like this:
tv1.setText(myText);
imgView.setImageResource(myImage);
This worked perfectly well while I only had a few images to test the logic, but once I added more images, I started to get the OutOfMemory error. If I make the images smaller, I get that error a little later in the process, but I still get it.
At first, I thought that Android does not release the previous source, so I thought using recycle() before the reassignment will help. Instead, I've got another error when I try to change the source:
Cannot draw recycled bitmaps
It looks like I am missing some vital understanding about how the ImageView handles the source images. Does it assign the bitmap reference and then keeps the same reference, but somehow changes content?
Then, after reading this article, I realized I have a different kind of problem altogether, the one that might be solved by using the inBitmap. Yet, the documentation says the bitmaps have to be the same size for that, and mine are not.
I am thinking of converting my drawable to bitmap, then scaling it to some hard-coded dimensions, then using inBitmap. I guess my second question is - does this approach make sense? Are there any downfalls in scaling the images? And any examples would be appreciated, of course.
Thank you!
In my activity I have
final ImageView img = (ImageView) findViewById(R.id.myimage);
img.setImageDrawable(getdrawable()); //getdrawable() is my own function which returns a drawable
Then this line
img.setImageDrawable(getdrawable()); //this line is
is called many times to replace the drawable on the current imageview.
Will I need to recycle the drawable or will the GC do it for me? and if i do need to recycle then how do i do it?
Drawables, like anything else that uses memory, get marked for garbage collection whenever there are no longer any references pointing to them. If you're running into a situation where memory isn't released as soon as you drop the reference to the previous drawable, well, that's GarbageCollector for you. GC will try to do its work in the most efficient way possible, which means that, from your perspective, it gets to it when it gets to it, and you can't make it go any faster, even if you call the object's dispose() method.
Read this guide for a really good explanation of what's going on under the covers with garbage collection.
Here are some newbie memory management observations to which I would like
hear an experienced opinion.
It seems that setting android:backgound="#drawable/xyz" in a xml
layout causes memory loss in my app. The respective activities keep
stacking up until I get an OOM error. This is especially true if I rotate
the device orientation.
However, if I load the same resource with setBackgoundResource(), and then clear the
callback and set the background reference to null, there is no leak whatsoever.
that is, first in onCreate()
mMainLayout.setBackgroundResource(R.drawable.background_general_android);
and then in onDestroy()
mMainLayout.getBackground().setCallback(null);
mMainLayout.setBackgroundDrawable(null);
Is this roughly correct, or am I missing something essential?
This would only happen if you keep a copy of the drawables in a static cache for instance. You might also be leaking your activities and setting the drawables to null simply hides the problem for a little longer. You should use a tool like MAT to inspect the content of your heap and figure out what's going on.
I have an activity which is playing a beat and alternating drawables from my drawables folder in tandem. Unfortunately the loading of the drawables is too slow and it goes out of sync.
Does anyone have a proposed solution?
I think it's common to load all your bitmaps ahead of time (perhaps with BitmapFactory.decodeResources()). Once loaded, just hang on to those references for your drawing.
Does that answer your question?
The answer is in the question. You can create a class that acts like a bitmap cache that uses a backing HashMap to save references to the decoded bitmaps. The simplest thing to do is to cache the full size bitmap and just key it with the generated int id from R.java. I did something similar, but I included the resizing logic and keyed it by a string in the form resourceid:width:height. You can provide a remove method and a clear method if you want to make sure you keep your memory down, but remember to recycle your bitmaps for older versions of the OS.