I read a lot about memory leaks in the last few days, and came across some interesting stuff.
I saw this answer to a basic Android bitmap-related memory leak question (the answer is from 2011) and I was wondering if this is still the case.
If I'm using views that contain bitmaps in my activity (ImageViews, TextViews...), do I really need to unbind their drawables when destroying the activity?
Is this only in some cases or always?
It's no longer necessary as of 4.0, as the callback is now stored in a WeakReference.
From 2.3.7:
public final void setCallback(Callback cb) {
mCallback = cb;
}
and in 4.0.1:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
Related
I am using the following post to generate a memory leak in a test application
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
I am using android studio memory profiler and allocation tracker to track the object allocation. I am able to see activity instances created while rotating the screen multiple times. But when I click on "Initiate GC" on android studio all these instances are garbage collected though they hold a static reference to the drawable object. I was expecting these activity objects to be retained and will cause an "Out of memory" exception. Below is the code I have used :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
textView.setText("Hello View");
if(sBackground == null) {
sBackground = ContextCompat.getDrawable(this,R.drawable.back1mb);
}
mTextView = (TextView) findViewById(R.id.txtView);
textView.setBackgroundDrawable(sBackground);
setContentView(textView);
}
The problem is, the blog post you've referenced is very old, and the Android SDK has changed a lot since it was written. In the early days, as the tutorial says:
When a Drawable is attached to a view, the view is set as a callback on the drawable.
However, this isn't true for more recent versions of the Android SDK.
The code for the early version of Drawable.setCallback was (see link):
public final void setCallback(Callback cb) {
mCallback = cb;
}
But it now uses a WeakReference (see link), so won't leak any more:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
You could build against an old version of Android to see the leak behaviour in the blog, or use a different means of creating a leak.
I am writing a custom view by directly inheriting from the View class, and I am wondering whether I am making a good use of the WeakReference class. First, this is the most relevant part of my class :
public class ChessView extends View {
public ChessView(Context context, AttributeSet attrs, int defStyle) {
/* some code removed */
invalidateHandler = new InvalidateHandler(this);
new Thread(new Runnable() {
#Override
public void run() {
invalidateHandler.sendMessage(invalidateHandler.obtainMessage());
}
}).start();
}
/* some code removed */
private static class InvalidateHandler extends Handler {
public InvalidateHandler(ChessView view){
relatedChessView = new WeakReference<ChessView>(view);
}
#Override
public void handleMessage(Message msg) {
relatedChessView.get().invalidate();
}
private WeakReference<ChessView> relatedChessView;
};
private InvalidateHandler invalidateHandler;
}
As you can see :
I am creating a static inner class, subclass of the Handler class : as the android developpers guide recommands to avoid direct inner classes inside View subclasses
The Handler static inner class makes a call to the invalidate() method of my custom ChessView : so I decided to "wrap it" inside a WeakReference, as the android developper guide recommands to avoid hard references on View instances.
So here my questions :
Do I avoid memory leaks this way ?
Is WeakReference the best type, or should I use a SoftReference instead ?
And finally, will the custom view remains on the heap as long as the view is visible (or the related activity active) or may it be collected by the GC before, letting me with a null reference when calling relatedChessView.get() ?
Thanks in advance, and apologizes if my question is bad formulated.
Do I avoid memory leaks this way ?
Yes, but this isn't necessary.
Is WeakReference the best type, or should I use a SoftReference instead ?
WeakReference is the way to go in your case. SoftReference and WeakReference will be both available as long as there is a hard reference pointing to them. If there is no strong reference though, WeakReference will more likely to be collected while SoftReference will retain your object as long as there is no need to clean up memory (eg. SoftReference will stick around longer).
And finally, will the custom view remains on the heap as long as the view is visible (or the related activity active) or may it be collected by the GC before, letting me with a null reference when calling relatedChessView.get()?
Yes, it will. As i mentioned above WeakReference won't be collected while there are any Objects holding the contained Object's reference.
UPDATE: Fixed the information regarding Weak and Soft references based on #DeeV's answer to be more accurate.
I just read a blogpost by Romain Guy on how to avoid memory leaks in Android.
In the article he gives this example:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
Romain said:
This example is one of the simplest cases of leaking the Context.
My question is, how do you modify it correctly?
Just like this?
TextView label = new TextView(Context.getApplicationContext());
I tested both ways and the results are the same. I can't locate the difference. And I think that this is more correct than the Application context. Because this is a reference to Activity, that is to say, the TextView belongs to that Activity.
Could someone give me an explanation for this?
The actual problem with that code isn't the context passed to create the drawable, but private static Drawable sBackground;
The static Drawable is created with the Activity as the context, so in THAT case, there's a static reference to a Drawable that references the Activity, and that's why there's a leak. As long as that reference exists, the Activity will be kept in memory, leaking all of its views.
So it's the Drawable which should be created using the application context, not the TextView. Creating the TextView with "this" is perfectly fine.
edit : Actually, that might not make a big difference, the problem is that once the drawable is binded to a view, there's a reference to the view, which references the activity. So you need to "unbind" the drawable when you exit the activity.
I'm not sure if Romain had updated his blog entry since you read it, but he's pretty clear on how to avoid the leaks, even pointing you to an example in the Android OS. Note that I fixed the broken link in Romain's blog entry via archive.org.
This example is one of the simplest cases of leaking the Context and
you can see how we worked around it in the Home screen's source
code (look for the unbindDrawables() method) by setting the stored
drawables' callbacks to null when the activity is destroyed.
Interestingly enough, there are cases where you can create a chain of
leaked contexts, and they are bad. They make you run out of memory
rather quickly.
There are two easy ways to avoid context-related memory leaks. The
most obvious one is to avoid escaping the context outside of its own
scope. The example above showed the case of a static reference but
inner classes and their implicit reference to the outer class can be
equally dangerous. The second solution is to use the Application
context. This context will live as long as your application is alive
and does not depend on the activities life cycle. If you plan on
keeping long-lived objects that need a context, remember the
application object. You can obtain it easily by calling
Context.getApplicationContext() or Activity.getApplication().
In summary, to avoid context-related memory leaks, remember the
following:
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the
activity itself)
Try using the context-application instead of a context-activity
Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
A garbage collector is not an insurance against memory leaks
Memory leaks at that code mostly happen when you rotate your screen (that is, changing the orientation state) so your activity was destroyed and created again for the new orientation. There's a lot of explanation about memory leaks.
You can take a look at one of the Google I/O 2011 video about Memory Management here. In the video, you can also use the memory management tools like Memory Analyzer available to download here.
I don't know if you are having any trouble with this in your app, but I have created a drop in solution that fixes all the android memory leak issues with standard android classes: http://code.google.com/p/android/issues/detail?id=8488#c51
public abstract class BetterActivity extends Activity
{
#Override
protected void onResume()
{
System.gc();
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
System.gc();
}
#Override
public void setContentView(int layoutResID)
{
ViewGroup mainView = (ViewGroup)
LayoutInflater.from(this).inflate(layoutResID, null);
setContentView(mainView);
}
#Override
public void setContentView(View view)
{
super.setContentView(view);
m_contentView = (ViewGroup)view;
}
#Override
public void setContentView(View view, LayoutParams params)
{
super.setContentView(view, params);
m_contentView = (ViewGroup)view;
}
#Override
protected void onDestroy()
{
super.onDestroy();
// Fixes android memory issue 8488 :
// http://code.google.com/p/android/issues/detail?id=8488
nullViewDrawablesRecursive(m_contentView);
m_contentView = null;
System.gc();
}
private void nullViewDrawablesRecursive(View view)
{
if(view != null)
{
try
{
ViewGroup viewGroup = (ViewGroup)view;
int childCount = viewGroup.getChildCount();
for(int index = 0; index < childCount; index++)
{
View child = viewGroup.getChildAt(index);
nullViewDrawablesRecursive(child);
}
}
catch(Exception e)
{
}
nullViewDrawable(view);
}
}
private void nullViewDrawable(View view)
{
try
{
view.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
try
{
ImageView imageView = (ImageView)view;
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
}
// The top level content view.
private ViewGroup m_contentView = null;
}
I am newbie to both Java and Android, and currently I am confused about "memory leak" in Android, for example: I have 01 Class, 01 Activity and 01 Interface as following:
Class BackGroundWorker is a singleton, which lives as long as the application lives:
public class BackGroundWorker {
private IOnEventOccurListener listener = null;
private static BackGroundWorker instance;
// ....
public void setListener(IOnEventOccurListener pListener) {
this.listener = pListener;
}
// ....
public static BackGroundWorker getInstance() {
//...
return instance;
}
}
The Listener Interface:
public interface IOnEventOccurListener {
public void onEventOccur();
}
And the Listener itself (An activity):
public class ShowSomething extends Activity implements IOnEventOccurListener{
BackGroundWorker bgWorker;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bgWorker = BackGroundWorker.getInstance();
bgWorker.setListener(this);
}
#Override
public void onEventOccur() {
// TODO do something
}
}
Now, according to what Romain Guy mentioned here:
It’s a memory leak, because there’s a reference to the listener (Activity). So Java GC cannot collect the Activity, even when it’s not in use.
I was able to solve that problem by WeakReference – but still wonder:
In this case, when the device needs more memory, according to Android Dev document, it will “kill” the activity if needed - assuming that the Activity ShowSomething is “killed” – then what happens ? (It’s still leak according to Romain Guy, and still “killed” )
I am really confused. Could anybody please explain this ?
Thank you in advanced,
Son
Android will destroy activities that are not on the screen to try to free up memory. However, GC rules still apply, and hence your static reference to the activity prevents the memory from being freed.
Eventually, Android will terminate the whole process. At that point, your leaked memory will be freed. However, in between your activity being destroyed and the process being terminated, you are wasting RAM.
Rather than use WeakReference, please null out the static reference when the activity is destroyed.
I think it would have the leak if we will use:
static BackGroundWorker bgWorker;
instead:
BackGroundWorker bgWorker;
With the release of Gingerbread, I have been experimenting with some of the new API's, one of them being StrictMode.
I noticed that one of the warnings is for getSharedPreferences().
This is the warning:
StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2
and it's being given for a getSharedPreferences() call being made on the UI thread.
Should SharedPreferences access and changes really be made off the UI thread?
I'm glad you're already playing with it!
Some things to note: (in lazy bullet form)
if this is the worst of your problems, your app's probably in a good spot. :) Writes are generally slower than reads, though, so be sure you're using SharedPreferenced$Editor.apply() instead of commit(). apply() is new in GB and async (but always safe, careful of lifecycle transitions). You can use reflection to conditionally call apply() on GB+ and commit() on Froyo or below. I'll be doing a blogpost with sample code of how to do this.
Regarding loading, though...
once loaded, SharedPreferences are singletons and cached process-wide. so you want to get it loaded as early as possible so you have it in memory before you need it. (assuming it's small, as it should be if you're using SharedPreferences, a simple XML file...) You don't want to fault it in the future time some user clicks a button.
but whenever you call context.getSharedPreferences(...), the backing XML file is stat'd to see if it's changed, so you'll want to avoid those stats during UI events anyway. A stat should normally be fast (and often cached), but yaffs doesn't have much in the way of concurrency (and a lot of Android devices run on yaffs... Droid, Nexus One, etc.) so if you avoid disk, you avoid getting stuck behind other in-flight or pending disk operations.
so you'll probably want to load the SharedPreferences during your onCreate() and re-use the same instance, avoiding the stat.
but if you don't need your preferences anyway during onCreate(), that loading time is stalling your app's start-up unnecessarily, so it's generally better to have something like a FutureTask<SharedPreferences> subclass that kicks off a new thread to .set() the FutureTask subclasses's value. Then just lookup your FutureTask<SharedPreferences>'s member whenever you need it and .get() it. I plan to make this free behind the scenes in Honeycomb, transparently. I'll try to release some sample code which
shows best practices in this area.
Check the Android Developers blog for upcoming posts on StrictMode-related subjects in the coming week(s).
Accessing the shared preferences can take quite some time because they are read from flash storage. Do you read a lot? Maybe you could use a different format then, e.g. a SQLite database.
But don't fix everything you find using StrictMode. Or to quote the documentation:
But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread are almost always a problem, though.
One subtlety about Brad's answer: even if you load the SharedPreferences in onCreate(), you should probably still read values on the background thread because getString() etc. block until reading the shared file preference in finishes (on a background thread):
public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
edit() also blocks in the same way, although apply() appears to be safe on the foreground thread.
(BTW sorry to put this down here. I would have put this as a comment to Brad's answer, but I just joined and don't have enough reputation to do so.)
I know this is an old question but I want to share my approach. I had long reading times and used a combination of shared preferences and the global application class:
ApplicationClass:
public class ApplicationClass extends Application {
private LocalPreference.Filter filter;
public LocalPreference.Filter getFilter() {
return filter;
}
public void setFilter(LocalPreference.Filter filter) {
this.filter = filter;
}
}
LocalPreference:
public class LocalPreference {
public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
int maxAge, boolean showMale, boolean showFemale) {
Filter filter = new Filter();
filter.setMaxDistance(maxDistance);
filter.setMinAge(minAge);
filter.setMaxAge(maxAge);
filter.setShowMale(showMale);
filter.setShowFemale(showFemale);
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
babysitApplication.setFilter(filter);
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
}
public static Filter getLocalPreferences(Activity activity) {
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
Filter applicationFilter = babysitApplication.getFilter();
if (applicationFilter != null) {
return applicationFilter;
} else {
Filter filter = new Filter();
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
babysitApplication.setFilter(filter);
return filter;
}
}
public static class Filter {
private int maxDistance;
private int minAge;
private int maxAge;
private boolean showMale;
private boolean showFemale;
public int getMaxDistance() {
return maxDistance;
}
public void setMaxDistance(int maxDistance) {
this.maxDistance = maxDistance;
}
public int getMinAge() {
return minAge;
}
public void setMinAge(int minAge) {
this.minAge = minAge;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
public boolean isShowMale() {
return showMale;
}
public void setShowMale(boolean showMale) {
this.showMale = showMale;
}
public boolean isShowFemale() {
return showFemale;
}
public void setShowFemale(boolean showFemale) {
this.showFemale = showFemale;
}
}
}
MainActivity (activity that get called first in your application):
LocalPreference.getLocalPreferences(this);
Steps explained:
The main activity calls getLocalPreferences(this) -> this will read your preferences, set the filter object in your application class and returns it.
When you call the getLocalPreferences() function again somewhere else in the application it first checks if it's not available in the application class which is a lot faster.
NOTE: ALWAYS check if an application wide variable is different from NULL, reason -> http://www.developerphil.com/dont-store-data-in-the-application-object/
The application object will not stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.
If I didn't check on null I would allow a nullpointer to be thrown when calling for example getMaxDistance() on the filter object (if the application object was swiped from the memory by Android)
SharedPreferences class does some reads & writes within XML files on disk, so just like any other IO operation it could be blocking. The amount of data currently stored in SharedPreferences affects the time and resource consumed by the API calls. For minimal amounts of data it's a matter of a few milliseconds (sometimes even less than a millisecond) to get/put data. But from the point of view of an expert it could be important to improve the performance by doing the API calls in background. For an asynchronous SharedPreferences I suggest checking out the Datum library.
i do not see any reason to read them from a background thread. but to write it i would. at startup time the shared preference file is loaded into memory so its fast to access, but to write things can take a bit of time so we can use apply the write async. that should be the difference between commit and apply methods of shared prefs.