Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 will. 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...

like image 334
Oleksandr Avatar asked Dec 03 '22 02:12

Oleksandr


2 Answers

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
like image 143
Michael Avatar answered Dec 17 '22 09:12

Michael


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.

like image 37
mreichelt Avatar answered Dec 17 '22 08:12

mreichelt