【问题标题】:View + Tag = memory leak?查看+标签=内存泄漏?
【发布时间】:2011-03-23 20:04:50
【问题描述】:

依据:

  • 活动 - 在每次方向更改时重新创建(onCreate-onDestroy)
  • View 由 ViewFlipper 和两个孩子组成:简单的 RelativeLayout 和 ListView
  • ListView 行具有复杂的布局和关联的标签

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

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

但是视图和 GC 是如何处理标签的呢?我的标签对象(持有人)包含对视图的引用,但如果视图删除对其标签的引用,则此引用(带有标签本身)将很容易收集。

有人遇到过类似的 ListViews 问题吗?

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

【问题讨论】:

    标签: android memory-leaks view


    【解决方案1】:

    首先,如果您使用View.setTag(int, Object) 方法,您可以泄漏对象。使用此方法设置的标签存储在静态WeakHashMap 中,View 作为键。因此,如果您在父视图的标签中存储对子视图的引用,那么所有这些视图和它们创建的上下文(父活动)都将被泄露。这是因为每个子视图都持有对其父视图的引用,因此父视图永远不会被 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 类似乎会导致内存泄漏。这意味着列表视图,其所有回收的子项及其父活动都被泄露了。以下是有关此错误的一些信息:

    【讨论】:

    • 如果我错了,请纠正我,但我相信标签现在存储在 SparseArray 中。
    • @MM。是的,你是对的。似乎现在将视图存储在键控标签中并不那么危险。谢谢你的评论。
    【解决方案2】:

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

    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 引用、进度条等都清空。

      我使用的一个好的模式是有一个“StateHolder”内部类,它确实包含一个 Activity 引用,但我实现了一个 setActivityForTasks 方法,我只是将 NULL 传递给它,它反过来将所有 Activity 引用设置为 NULL。然后,当您在方向更改后返回 Activity 时,您只需调用 setActivityForTasks(this) 即可重置当前 Activity。

      唯一的收获就是清空对onRetainNonConfigurationInstance中任何与Activity相关的任何引用

      【讨论】:

        【解决方案4】:

        在 Gingerbread 和更低版本的 Android 中,View.setTag (int key, Object tag) 会泄漏内存。不要使用它。它已在 ICS 中修复。

        【讨论】:

          猜你喜欢
          • 2019-10-11
          • 2016-12-04
          • 2011-05-25
          • 1970-01-01
          • 1970-01-01
          • 2017-11-29
          • 2011-05-21
          • 1970-01-01
          • 2014-12-01
          相关资源
          最近更新 更多