Как предотвратить утечку памяти при использовании динамических экранов?

Я создаю приложение с экранами, которые представляют данные пользователю. Каждый Screen имеет свои собственные данные и свой собственный макет, поэтому у него есть метод для возврата int, который представляет макет, который используется для его расширения, затем этот View передается функции для поиска конкретных представлений и заполнения их данными.

Жизненный цикл выглядит следующим образом: MainPresenter:

screen.getNextScreen ->
screen.getLayout -> 
view = inflateScreen ->
screen.populateScreen(view) ->
(wait for time elappsed or click) -> repeat

Эти Screens также нужны в SettingsActivity для их включения \ отключения.

Итак, я создал синглтон ScreenProvider, он инициализируется один раз, а затем возвращает список.

public class ScreenProvider {

    private List<Screen> screens;

    private static ScreenProvider instance = new ScreenProvider();

    public static ScreenProvider getInstance(){
        return instance;
    }

    private ScreenProvider() {
        screens = new ArrayList<>();

        screens.add(new Welcome());
        screens.add(new CompoundScreen());
        screens.add(new Times());
        screens.add(new Messages());
        screens.add(new Weekly());
    }

    public List<Screen> getScreenList() {
        return Lists.newArrayList(screens);
    }
}

Дело в том, что при длительной работе приложение вылетает или закрывается из-за утечек памяти, поэтому я добавил утечку, и это пример для его отчета:

MainActivity has leaked:
D: * static ScreenProvider.!(instance)!
D: * ↳ ScreenProvider.!(screens)!
D: * ↳ ArrayList.!(array)!
D: * ↳ array Object[].!([0])!
D: * ↳ CompoundScreen.!(disposable)!
D: * ↳ LambdaObserver.!(onNext)!
D: * ↳ -$$Lambda$Screen$67KdQ1jl3VSjSvoRred5JqLGY5Q.!(f$1)!
D: * ↳ AppCompatTextView.mContext
D: * ↳ MainActivity

Это всего лишь единичный пример, но почти на каждом экране есть такая утечка. Отчет LeakCanary показывает, что TextView имеет это: D: | mAttachInfo = null, поэтому я предполагаю, что это не проблема. Также каждый Screen имеет onHide() для очистки одноразового использования, который вызывается, когда текущий Screen скрывается и находится в MainActivity.onStop().

Как исправить эту утечку? Не стоит ли использовать синглтон для экранов? Если нет, как мне получить доступ к списку экранов из других действий?

** Редактирование ** Добавление некоторых Screen основных методов, которые отменяются на каждом экране.

public abstract int getLayout();

public boolean shouldShow()

public void populateData(View view)

public void onHide()

public abstract int getScreenIndex();

public boolean shouldCacheView()

public int getDuration()

person SnapDragon    schedule 10.04.2019    source источник
comment
android.jlelse.eu/   -  person Chaudhary Amar    schedule 10.04.2019
comment
Есть ли причина, по которой вы управляете всеми своими экранами в MainActivity? Звучит совсем нехорошо. Чем все закончится, если вы продолжите добавлять экраны? Я бы использовал разные фрагменты и презентаторы с каждого экрана, со своим жизненным циклом, бизнес-логикой и т. Д.   -  person David Miguel    schedule 15.04.2019
comment
Является ли экран интерфейсом или оболочкой, предназначенной для предоставления представления, или представлением само по себе? Тот же вопрос для "Добро пожаловать" и для остальных   -  person Fco P.    schedule 17.04.2019
comment
@DavidMiguel - это Activity с одним FrameLayout, который присоединяет представление, удаляет его и присоединяет еще один. В MainPresenter представление увеличено. Я думал, что когда представление будет удалено, оно будет собрано мусором, за исключением тех, которые я помечу как кешированные. @FcoP. Это обертка. У него есть метод, который получает представление и заполняет его данными. Я буду редактировать, чтобы добавить Screen код smaple.   -  person SnapDragon    schedule 18.04.2019
comment
@DavidMiguel Я думал, что у Fragment излишне сложный жизненный цикл.   -  person SnapDragon    schedule 18.04.2019
comment
Если это оболочка, удерживаете ли вы / создаете фактические представления внутри экрана? Кроме того, можете ли вы опубликовать соответствующий код MainActivity?   -  person Fco P.    schedule 18.04.2019
comment
@SnapDragon Вы пишете every Screen has an onHide() to clear disposables, но я предполагаю, что вы очищаете одноразовые предметы неправильно или, возможно, не сохраняете их / не добавляете в составные одноразовые. Можете ли вы показать сделанные вами подписки в CompoundScreen?   -  person wasyl    schedule 20.04.2019


Ответы (1)


Хорошо. Судя по тому, что вы говорите и что показываете, кажется, что вы сохраняете экземпляры некоторых сгенерированных представлений в синглтоне. Не надо. Каждое представление требует создания контекста либо с помощью кода, либо с помощью инфляции (который в основном является фабричным методом на основе XML и отражением) для доступа к ресурсам приложения и системы и сохранения ссылки на указанный контекст, поскольку пока они живы. В вашем сценарии это означает сохранение ссылок на действие, в котором вы создали представление. Обычно, что касается просмотров и действий, с GC происходит следующее:

GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference!
GC: Okay... and besides MainActivity, Does anybody else need this View class?
-Nobody answers-
GC: It does not matter my friend, you are being collected as well. Come with me.
And they both go.

В твоем случае:

GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference! and MainActivity references me as well.
GC: Okay... and besides MainActivity, Does anybody else need this View class?
ScreenProvider: I do.
GC: Okay, keep moving View, and take MainActivity with you. Let me know when you folks are done so I can collect you.

And thus the leak.

Чтобы передать представление из одного действия в другое, вам необходимо удалить ссылку (поле mContext) на предыдущее действие. Поскольку для этого нет API, вам нужно будет использовать отражение. И возникает еще одна проблема: каждая часть пользовательского интерфейса является подклассом View. Макеты, виджеты и т.п. никаких дочерних просмотров на любом уровне. После этого вам нужно будет таким же образом установить ссылку на новое действие. Это звучит как серьезный взлом, потому что это так, и на каком-то уровне все обязательно сломается. В конце концов, контекст представляет собой среду и состояние, существующее в вашем представлении.

Лучшее решение для вашей ситуации - удалить ссылки на представления из синглтона и использовать его только для сохранения представлений о состоянии / конфигурации данного представления. Создайте метод с поддержкой обратного вызова (или аналогичный), который расширяет представление в фоновом режиме и выполняет необходимые настройки перед возвратом указанного представления. Если вы по-прежнему хотите сохранить единый репозиторий всех экранов, которые может иметь действие, добавьте его в класс активности в качестве члена, чтобы он собирался вместе с действием.

В качестве примечания: ваша ситуация предлагает вам использовать одно действие, а затем просто поменять местами «MainScreen», состоящий из «Screens», или просто переключаться между экранами в зависимости от ситуации. В этом было бы больше смысла и было бы менее рискованно.

Наконец, цитирую себя: Помните первое правило бойцовского клуба Android

person Fco P.    schedule 21.04.2019
comment
Привет, извините за поздний ответ, офисы были закрыты. Вы можете объяснить лучшее решение? - person SnapDragon; 30.04.2019