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

Фрагменты Android в Backstack занимают слишком много памяти

Проблема:

У меня есть приложение для Android, которое позволяет пользователю просматривать профиль пользователя ViewProfileFragment. Внутри ViewProfileFragment пользователь может щелкнуть изображение, которое приведет его к StoryViewFragment, где появятся фотографии разных пользователей. Можно щелкнуть по фотографии профиля пользователя, которая перенесет их в другой экземпляр ViewProfileFragment с новым профилем пользователя. Если пользователь повторно нажимает на пользовательские профили, щелкает изображение, которое выводит их в галерею, затем нажимает на другой профиль, который фрагменты складываются в память, быстро вызывая страшный OutOfMemoryError. Вот диаграммный поток того, что я описываю:

UserA нажимает на профиль Боба. Внутри профиля Bob UserA нажимает на ImageA, беря его в галерею фотографий различных пользователей (включая Боба). UserA нажимает на профиль Sue, затем на одном из ее изображений - повторы процесса и т.д. И т.д.

UserA -> ViewProfileFragment
         StoryViewFragment -> ViewProfileFragment
                               StoryViewFragment -> ViewProfileFragment

Итак, как вы можете видеть из типичного потока, в стопке складок много экземпляров ViewProfileFragment и StoryViewFragment.

СООТВЕТСТВУЮЩИЙ КОД

Я загружаю их в виде фрагментов со следующей логикой:

//from MainActivity
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.activity_main_content_fragment, fragment, title);
ft.addToBackStack(title);

ЧТО Я ПОЛУЧИЛ

1) Я специально использую FragmentTransaction replace, чтобы метод onPause запускался, когда имеет место replace. Внутри onPause Я пытаюсь освободить столько ресурсов, сколько могу (например, очистка данных в адаптерах ListView, "обнуление" переменных и т.д.), Так что, когда фрагмент не является активным фрагментом и нажимается на backstack будет больше освобожденной памяти. Но мои усилия по освобождению ресурсов - это лишь частичный успех. Согласно MAT, у меня все еще много памяти, которая потребляется GalleryFragment и ViewProfileFragment.

2) Я также удалил вызов addToBackStack(), но, очевидно, это плохой пользовательский интерфейс, потому что они не могут вернуться назад (приложение просто закрывается, когда пользователь нажимает кнопку "Назад" ).

3) Я использовал MAT для поиска всех объектов, которые занимают много места, и я использовал их разными способами внутри методов onPauseonResume), чтобы освободить ресурсы, но они по-прежнему значительны по размеру.

enter image description here

4) Я также написал цикл for в обоих фрагментах onPause, который устанавливает все мои ImageViews в null, используя следующую логику:

 for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) {

     View h = shell.getChildAt(i);
     ImageView v = (ImageView) h.findViewById(R.id.galleryImage);
       if (v != null) {
           v.setImageBitmap(null);
       }
  }

myListViewAdapter.clear()

ВОПРОСЫ

1) Могу ли я игнорировать способ позволить фрагменту оставаться на задней стороне, но также освобождать его ресурсы, так что цикл .replace(фрагмент) не съедает всю мою память?

2) Каковы "лучшие практики", когда ожидается, что много фрагментов может быть загружено в заднюю часть? Как разработчик правильно справляется с этим сценарием? (Или логика в моем приложении по своей сути ошибочна, и я просто делаю это неправильно?)

Любая помощь в мозговом штурме решения этого будет очень признательна.

4b9b3361

Ответ 1

Оказывается, что фрагменты используют один и тот же жизненный цикл, как и их родительская активность. Согласно Документация фрагментов:

Фрагмент всегда должен быть встроен в активность, а фрагмент жизненный цикл напрямую зависит от жизненного цикла хоста. Для Например, когда действие приостановлено, все фрагменты в нем и когда действие уничтожается, так же как и все фрагменты. Однако, хотя выполняется действие (оно находится в состоянии возобновленного жизненного цикла), вы можете манипулировать каждым фрагментом независимо.

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

Возможно, вы сможете решить свою проблему, не полагаясь на onPause, но переопределив setUserVisibleHint на фрагменте. Это дает вам хорошее место, чтобы определить, где выполнить настройку ресурсов или очистить ресурсы, когда фрагмент входит и выходит из вида (например, когда у вас есть PagerAdapter, который переключается с FragmentA на FragmentB).

public class MyFragment extends Fragment {
  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
      //you are visible to user now - so set whatever you need 
      initResources();
    }
    else { 
     //you are no longer visible to the user so cleanup whatever you need
     cleanupResources();
    }
  }
}

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

Другое предложение заключается в том, чтобы получить хорошее представление о выходе инструмента анализатора памяти (MAT) и анализа памяти в целом. Хорошая отправная точка. Это действительно легко утечка памяти в Android, поэтому мне, по-моему, необходимо ознакомиться с концепцией и как память может уйти от вас. Возможно, ваши проблемы связаны с тем, что вы не освобождаете ресурсы, когда фрагмент выходит из поля зрения, а также некоторая утечка памяти, поэтому, если вы пройдете маршрут использования setUserVisibleHint, чтобы инициировать очистку ваших ресурсов, и вы по-прежнему видите большой объем используемой памяти, тогда утечка памяти может быть преступником, поэтому убедитесь, что вы оба вышли из нее.

Ответ 2

Трудно видеть всю картину (даже если вы показали нам много информации), без конкретного доступа к исходному коду, что, я уверен, было бы непрактичным, если не невозможным.

Говоря, есть несколько вещей, которые следует иметь в виду при работе с Фрагментами. Сначала часть отказа от ответственности.

Когда Фрагменты были введены, они звучали как лучшая идея всех времен. Возможность одновременного отображения нескольких видов деятельности. Это был пункт продажи.

Итак, весь мир медленно начал использовать Фрагменты. Это был новый ребенок на блоке. Все использовали Фрагменты. Если вы не использовали Фрагменты, скорее всего, "вы делали это неправильно".

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

Итак, в резюме: Я ненавижу фрагменты. Я считаю, что это одна из худших реализаций Android всех времен, которая только завоевала популярность из-за отсутствия Transition Framework (как она существует сегодня) между действиями. Жизненный цикл фрагмента - это, во всяком случае, шар случайных обратных вызовов, которые никогда не гарантируются, когда вы их ожидаете.

(Хорошо, я немного преувеличиваю, но задаю любому разработчику Android, если у него возникли проблемы с фрагментами в какой-то момент, и ответ будет громким да).

При всем том, что говорят, Фрагменты работают. И поэтому ваше решение должно работать.

Итак, давайте начнем смотреть, кто/где может хранить эти жесткие ссылки.

note. Я просто собираюсь разобраться в том, как отлаживать это, но вряд ли я дам прямое решение. Используйте его как ссылку.

ЧТО ПРОИСХОДИТ?: Вы добавляете фрагменты в Backstack. В задней стопке хранится твердая ссылка на Фрагмент, а не слабая или мягкая. (источник)

Теперь кто хранит стопку? FragmentManager и... как вы уже догадались, он использует также живую ссылку (источник).

И, наконец, каждое действие содержит жесткую ссылку на FragmentManager.

Вкратце: пока ваша деятельность не умрет, все ссылки на ее фрагменты будут существовать в памяти. Независимо от операций добавления/удаления, которые выполнялись на уровне Fragment Manager/backstack.

ЧТО ВЫ МОЖЕТЕ СДЕЛАТЬ? Мне приходит в голову несколько вещей.

  • Попробуйте использовать простой загрузчик изображений/кеш-память, например Picasso, если что-нибудь, чтобы убедиться, что изображения не просочились, Вы можете позже удалить его, если хотите использовать свою собственную реализацию. При всех своих недостатках Пикассо действительно прост в использовании и пришел к состоянию, где он имеет дело с памятью "правильный путь".

  • После того, как вы удалили проблему "Я могу протекать растровые изображения" из изображения (каламбур не предназначен!), пришло время вернуться к жизненному циклу фрагмента. Когда вы кладете фрагмент в backstack, он не уничтожается, но... у вас есть шанс очистить ресурсы: вызывается Fragment#onDestroyView(). И здесь вы хотите убедиться, что фрагмент аннулирует любые ресурсы.

Вы не упоминаете, используете ли ваши фрагменты setRetainInstance(true), будьте осторожны с этим, потому что они не уничтожаются/воссоздаются, когда активность уничтожается/воссоздается (например: вращение), и все представления могут быть пропущены, если не надлежащим образом обработаны.

  1. Наконец, но это сложнее диагностировать, может быть, вы захотите вернуться к своей архитектуре. Вы запускаете один и тот же фрагмент (viewprofile) несколько раз, вы можете рассмотреть вместо этого, повторно использовать один и тот же экземпляр и загрузить в него "нового пользователя". Backstack можно обрабатывать, отслеживая список пользователей в том порядке, в котором они загружены, поэтому вы можете перехватить onBackPressed и переместить "вниз" в стек, но всегда загружать новые/старые данные по мере того, как пользователь переходит. То же самое касается вашего StoryViewFragment.

В целом, это все предложения, которые пришли из моего опыта, но вам очень тяжело помочь, если мы не сможем увидеть более подробно.

Надеюсь, это станет отправной точкой.

Желаем удачи.