查看视图和标签是否会导致内存泄漏?

4

基础:

  • Activity - 在每次屏幕方向改变时重建(onCreate-onDestroy)
  • 视图包含一个ViewFlipper,其中包含两个子项:简单的RelativeLayout和ListView
  • ListView行具有复杂的布局和相关标签

问题是每次屏幕方向改变都会出现内存泄漏 - Activity与整个视图布局一起留在内存中。Activity本身是一个上下文,因此只要关联对象存在,它就会一直留在内存中。所以现在我正在尝试找出为什么会发生泄漏。

View有setTag()方法。我使用它来存储有关行的一些信息(因此ListView中的每一行(View)都有相关的标签)。

但是视图和GC如何处理标签?我的标记对象(holders)包含对视图的引用,但是如果视图删除对其标签的引用,则这些引用(以及标签本身)将很容易被收集。

是否有人遇到过类似的ListView问题?

P.S. 我想知道GC如何清理布局 - 大量的循环引用、上下文、持有者等...

4个回答

9
首先,如果您使用View.setTag(int, Object)方法,则可能会泄漏对象。使用此方法设置的标记存储在带有View作为键的静态WeakHashMap中。因此,如果您将子视图的引用存储在父视图的标记中,则所有这些视图以及它们所创建的上下文(父活动)都将被泄漏。这是因为每个子视图都持有对其父视图的引用,因此父视图永远不会被GC收集。
有一种简单的方法可以模拟此行为:
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];
    }
}

其次,ListView类似乎会导致内存泄漏。这意味着列表视图、所有已回收的子项和其父活动都将被泄漏。以下是有关此错误的一些信息:

1
请纠正我,但我相信标签现在存储在SparseArray中。 - MM.
@MM。没错,你说得对。现在似乎将视图存储在键控标记中并不那么危险。感谢您的评论。 - Michael

4

我认为你可能有一些非静态内部类,它们总是保存指向其周围对象实例的指针。例如:

public class A {

    private class B {
        // ...
    }

    // b stores a reference to the instance of A
    private B b = new B();
}

如果您使用setTag()方法(例如ViewHolder类),请不要将任何对父对象的引用存储在其中。事实上,您应该将此类声明为静态。

此外,为了避免内存泄漏,如果可能的话,您应该始终将getApplicationContext()的结果传递给需要Context的方法 - 而不是Activity本身的引用。


3

在屏幕方向改变时泄漏对Activity的引用非常容易。有一些博客文章讨论了这个问题,我认为这些文章是必读的:

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

在您的onRetainNonConfigurationInstance方法中,简而言之,您只需要小心地将任何与View对象、Activity引用、进度条等相关的引用设置为null。一个好的模式是使用一个"StateHolder"内部类,它包含一个Activity引用,但我实现了一个setActivityForTasks方法,我只需将其传递给NULL,它就会将所有Activity引用设置为NULL。然后当您在方向更改后重新浏览Activity时,只需调用setActivityForTasks(this)来重置当前Activity即可。最重要的一点是,在onRetainNonConfigurationInstance中将任何与Activity相关的引用设置为NULL。

2
在 Android 的 Gingerbread 及更低版本中,View.setTag(int key, Object tag) 会导致内存泄漏。请勿使用该方法。该问题在 ICS 中得到了修复。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接