Как предотвратить утечки памяти при использовании динамических экранов? - PullRequest
5 голосов
/ 10 апреля 2019

Я создаю приложение с экранами, которые представляют данные пользователю. Каждый 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()

1 Ответ

0 голосов
/ 21 апреля 2019

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

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.Макеты, виджеты и т. Д., И т. Д. Таким образом, либо вы сохраняете ссылку на каждый фрагмент вашего XML-файла, чтобы удалить контекст с помощью отражения, либо вы просматриваете дочерний список представления, удаляете контекст и продолжаете, пока не появитсянет больше дочерних взглядов, на любом уровне.После этого вам нужно будет установить ссылку на новое действие таким же образом.Это звучит как огромный взлом, потому что это так, и вещи обязательно сломаются на каком-то уровне.В конце концов, Контекст представляет среду и состояние в вашем представлении.

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

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

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

...