View + Tag = утечка памяти? - PullRequest
4 голосов
/ 23 марта 2011

Основа:

  • Активность - воссоздает (onCreate-onDestroy) при каждом изменении ориентации
  • Представление состоит из ViewFlipper с двумя дочерними элементами: простой RelativeLayout и ListView
  • Строки ListView имеют сложную компоновку и связанные теги

Проблема в том, что у меня происходит утечка памяти при каждом изменении ориентации - активность остается в памяти с полным макетом представления. Сама деятельность является контекстом, поэтому она будет оставаться в памяти до тех пор, пока связанные объекты будут. Поэтому сейчас я пытаюсь выяснить, почему происходят утечки.

Представление имеет setTag () метод. Я использую его для хранения некоторой информации о строках (поэтому каждая строка (View) в ListView имеет связанные теги).

Но как представления и GC работают с тегами? Мои объекты тегов (держатели) содержат ссылки на представления, но если представление удаляет ссылку на свой тег, эти ссылки (вместе с самим тегом) будут легко собраны.

Кто-нибудь сталкивался с подобными проблемами с ListViews?

P.S. мне интересно, как GC очищает макеты - тонны циклических ссылок, контекстов, держателей и т. д ...

Ответы [ 4 ]

9 голосов
/ 22 августа 2011

Во-первых, вы можете пропустить объекты, если используете метод 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 вызывает утечку памяти. Это означает, что представление списка, все его переработанные дочерние элементы и родительская активность просочились. Вот некоторая информация об этой ошибке:

4 голосов
/ 23 марта 2011

Я думаю, у вас где-то могут быть нестатические внутренние классы, которые всегда сохраняют указатель на экземпляр окружающего их объекта. Например:

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 голосов
/ 23 марта 2011

Это легко просочиться ссылки на деятельность по изменению ориентации. Об этом есть несколько постов в блоге, которые я считаю обязательными для прочтения:

http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html

http://android -developers.blogspot.com / 2009/01 / избегая-памяти leaks.html

http://code.google.com/p/android/issues/detail?id=2391

В супер-словах в вашем методе onRetainNonConfigurationInstance вы просто хотите быть осторожными, чтобы обнулить любые ссылки на объекты View и, в свою очередь, ссылки Activity, индикаторы выполнения и т. Д.

Хорошим шаблоном, который я использую, является наличие внутреннего класса «StateHolder», который содержит ссылку Activity, но я реализую метод setActivityForTasks, которому я просто передаю NULL, он в свою очередь устанавливает все ссылки Activity на NULL. Затем, когда вы возвращаетесь к своей активности после изменения ориентации, вы можете просто позвонить setActivityForTasks(this), чтобы сбросить текущую активность.

Единственный вынос - это просто обнулить любые ссылки на что-либо. Деятельность, связанная с onRetainNonConfigurationInstance

2 голосов
/ 22 августа 2012

В Gingerbread и более ранних версиях Android, View.setTag (int key, Object tag) утечка памяти. Не используйте его. Это было исправлено в ICS.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...