View + Tag = memory leak?
The basis:
- Activity - recreates(onCreate-onDestroy) on each orientatin change
- View consists of ViewFlipper with two childs: simple RelativeLayout and ListView
- ListView rows have complex layout and associated tags
The problem is that i have memory leak on each orientation change - activity stays in memory with whole view layout. Activity itself is a context so it'll stay in memory as long as associated objects wil开发者_C百科l. So now i'm trying to find why leaks are happen.
View has setTag() method. I'm using it to store some information about rows(so every row(View) in ListView has associated tags).
But how does views and GC acts with tags ? My tag objects(holders) contains references to views but if view removes reference to it's tag this references(with tag itself) will be easily collected.
Anybody have faced similar problems with ListViews ?
P.S. i'm wondering how GC cleans layouts - tonns of cyclic references, contexts, holders, etc...
Firstly you can leak objects if you use View.setTag(int, Object)
method. Tags set using this method are stored in a static WeakHashMap
with a View
as a key. So if you store references to child view in the parent view's tags then all these views and a context they were created with (parent activity) will be leaked. It happens because every child view holds a reference to its parent, so the parent view will never be collected by the GC.
There's a simple way to simulate this behavior:
public static class MainActivity extends ListActivity {
private final WeakHashMap<Parent, Parent.Child> mMap =
new WeakHashMap<Parent, Parent.Child>();
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If parents were collected OOM error wouldn't be thrown.
// But they aren't collected so we get OOM here.
for (int i = 0; i < 10; ++i) {
Parent parent = new Parent();
mMap.put( parent, parent.mChild );
}
}
}
public static class Parent {
public final Child mChild = new Child();
public class Child {
private final byte[] mJunk = new byte[10*1024*1024];
}
}
Secondly it seems that the ListView
class causes a memory leak. It means that the list view, all its recycled children and its parent activity are leaked. Here's some information about this bug:
- http://code.google.com/p/android/issues/detail?id=12334
- Android: AlertDialog causes a memory leak
I think you might have some non-static inner classes somewhere, which always save a pointer to their surrounding object instance. For example:
public class A {
private class B {
// ...
}
// b stores a reference to the instance of A
private B b = new B();
}
If you use the setTag() method (e.g. for a ViewHolder class), never store any references to parent objects there. In fact, you should declare this class static.
Plus, to avoid memory leaks, if possible you should always pass the result of getApplicationContext() to methods that require a Context - and no reference to the Activity itself.
Its easy to leak references to the Activity on orientation change. There are a handful of blog posts about this - which I feel are required reading:
http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
http://code.google.com/p/android/issues/detail?id=2391
In a super nutshell in your onRetainNonConfigurationInstance
method you just want to be careful that you null out any references to View objects and in turn Activity references, Progress bars, etc.
A good pattern I use is having a "StateHolder" inner class which does contain an Activity reference, but I implement a setActivityForTasks
method, which I just pass NULL to, it in turn sets all Activity references to NULL. Then when you're going back through your Activity after the orientation change you can just call setActivityForTasks(this)
to reset the current activity.
The single take-away is just to NULL out any references to anything Activity related in onRetainNonConfigurationInstance
In Gingerbread and lower versions of Android, View.setTag (int key, Object tag)
leaks memory. Do not use it. It was fixed in ICS.
精彩评论