Подтвердить что ты не робот

Утечка канарейки, рециркуляция утечки mAdapter

Я решил, что настало время узнать, как использовать Leak Canary для обнаружения утечек в моих приложениях, и, как я всегда это делаю, я попытался реализовать его в своем проекте, чтобы действительно понять, как использовать этот инструмент. Реализация этого была достаточно простой, сложная часть читала то, что инструмент отбрасывает мне. У меня есть scrollview, который, похоже, накапливает память в диспетчере памяти, когда я просматриваю вверх и вниз (даже если он не загружает никаких новых данных), поэтому я подумал, что это хороший объект-кандидат для отслеживания утечек, вот результат:

введите описание изображения здесь

Похоже, что v7.widget.RecyclerView просачивает адаптер, а не мое приложение. Но это не может быть прав... правильно?

Здесь приведен код адаптера и класс, использующие его: https://gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e

Я начал щедрость за этот вопрос, потому что он снова появился после двух лет на совершенно другом приложении

4b9b3361

Ответ 1

Если адаптер живет дольше, чем RecyclerView, вам нужно очистить ссылку на адаптер в onDestroyView:

@Override
public void onDestroyView() {
    recyclerView.setAdapter(null);
    super.onDestroyView();
}

В противном случае адаптер будет содержать ссылку на RecyclerView который уже должен был не хватить памяти.

Если экран задействован в анимации перехода, вам действительно нужно сделать еще один шаг вперед и очистить адаптер только после отсоединения вида:

@Override
public void onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            recyclerView.setAdapter(null);
        }
    });
    super.onDestroyView();
}

Ответ 2

Я смог исправить это, переопределив RecyclerView. Это происходит потому, что RecyclerView никогда не отменяет себя от AdapterDataObservable.

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}

Ответ 3

Прежде всего, я ссылаюсь на этот файл.

Похоже, v7.widget.RecyclerView протекает адаптер, а не мое приложение. Но это не может быть правдой.... верно?

Это на самом деле ваш адаптер, который пропускает RecyclerView (и это довольно ясно видно по графику трассировки и названию действия LeakCanary). Однако я не уверен, является ли он "родительским" RecyclerView или вложенным в HourlyViewHolder, или и тем, и другим. Я думаю, что виноваты ваши ViewHolders. Делая их нестатическими внутренними классами, вы явно предоставляете им ссылку на класс вложенного адаптера, и это почти напрямую itemView адаптер с переработанными представлениями, поскольку родителем каждого itemView в ваших держателях является сам RecyclerView.

Моим первым предложением, чтобы решить эту проблему, было бы разделить ваши ViewHolders и Adapter, сделав их статическими внутренними классами. Таким образом, они не содержат ссылку на адаптер, поэтому ваше поле контекста будет для них недоступным, и это также хорошо, поскольку ссылки на контекст должны передаваться и сохраняться экономно (также во избежание больших утечек памяти). Когда вам нужен контекст только для получения строк, сделайте это где-нибудь еще, например, в конструкторе адаптера, но не сохраняйте контекст как член. Наконец, DayForecastAdapter кажется опасным: вы передаете один и тот же его экземпляр каждому HourlyViewHolder, что выглядит как ошибка.

Я думаю, что исправление дизайна и разделение этих классов должны избавить от этой утечки памяти

Ответ 4

Я не могу открыть ваше изображение и увидеть фактическую утечку, но если вы определяете локальную переменную для RecyclerView в Fragment и устанавливаете свой фрагмент retainInstanceState true, это может вызвать возможные утечки с вращением.

При использовании Fragment с retainInstance вы должны очистить все ваши ссылки ui в onDestroyView

@Override
public void onDestroyView() {
     yourRecyclerView = null;
     super.onDestroyView();
}

Здесь вы можете найти подробную информацию по этой ссылке: Сохраненные фрагменты с UI и утечками памяти