Когда фрагмент заменяется и помещается в задний стек (или удаляется), остается ли он в памяти?

Поведение похоже на то, как работают Activity? Например, с Activity это работает так:

Действие A запускает Действие B, пока B отображается на экране, система может удалить A из памяти, если это нужно системе. При нажатии НАЗАД A будет воссоздан в памяти, как если бы он никогда не покидал ее.

Я искал четкое объяснение того, что происходит с памятью с фрагментами, и ничего не нашел. Это работает так же? Например:

Действие C содержит фрагмент F в макете. Затем в какой-то момент F заменяется фрагментом G, но F остается в своем заднем стеке.

Будет ли F оставаться в памяти до тех пор, пока C не будет уничтожен, или он может быть удален системой по мере необходимости?

На самом деле я спрашиваю, есть ли у меня риск нехватки памяти, если у меня есть задний стек сложных фрагментов в одном действии?


person cottonBallPaws    schedule 12.12.2011    source источник
comment
Обратите внимание, что ваше первое предположение неверно: действие A никогда не будет удалено из памяти. См. commonsware.com/blog. /2011/10/03/   -  person Thierry-Dimitri Roy    schedule 01.03.2012
comment
@Thierry-DimitriRoy, невероятно... Я чувствую, что живу во лжи... Я почти не верю в это...   -  person cottonBallPaws    schedule 04.03.2012
comment
Система не убивает ни одного действия или фрагмента. Он может только убить весь процесс, чтобы восстановить память, когда она заканчивается.   -  person stdout    schedule 02.04.2019


Ответы (4)


Взгляните на это: BackStackRecord.Op.fragment

Именно так фрагменты хранятся в заднем стеке. Обратите внимание на живую ссылку, там не используются ни WeakReference, ни SoftReference.

Теперь это: FragmentManagerImpl.mBackStack

Именно здесь менеджер хранит задний стек. Простой ArrayList, а также никаких WR или SR.

И, наконец, это: Activity.mFragments

Это ссылка на менеджер фрагментов.

Сборщик мусора может собирать только объекты, которые не имеют активных ссылок (не доступны ни из одного потока). Это означает, что пока ваша активность не будет уничтожена (и поэтому ссылка FragmentManager не исчезнет), GC не сможет собрать ни один из фрагментов в заднем стеке.

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

Надеюсь это поможет.

PS Итак, вкратце: Да, вы можете исчерпать память, добавив Fragments в задний стек, а также добавив слишком много представлений для представления иерархии.

UPD Учитывая ваш пример, F останется в памяти, пока C не будет убит. Если C убит, а затем воскрес с другой конфигурацией, F также будет уничтожен и реинкарнирован в другом объекте. Таким образом, объем памяти F сохраняется до тех пор, пока C не потеряет состояние или не будет очищен задний стек.

person Ivan Bartsov    schedule 09.02.2012
comment
Однако обратите внимание, что когда фрагмент помещается в задний стек, вызывается его onDestroyView(). Если в этот момент вы очистите все основные выделения, удерживаемые фрагментом, у вас не должно быть проблем с памятью. - person hackbod; 11.02.2012
comment
Да, твердая точка. Объекты-фрагменты все равно будут занимать некоторое количество памяти, и, строго говоря, можно затопить память огромным количеством фрагментов, даже если логика очистки на месте, но вы определенно правы - если очистить правильно, в реальных условиях ( т.е. когда количество фрагментов разумно) фрагменты обратного стека не должны вызывать каких-либо значительных накладных расходов памяти. - person Ivan Bartsov; 12.02.2012
comment
@hackbod Что ты имеешь в виду под major allocations? - person theblang; 08.05.2014
comment
@mattblang Если позволите, скорее всего, Дайанна имела в виду что-то, требующее больших объемов памяти и не управляемое иерархией представлений фрагментов, наиболее ярким примером которой является Bitmaps. Если ваш фрагмент держит пару примитивов или String среднего размера - это, вероятно, нормально. Но избавьтесь от всего, несколько экземпляров которого превысят лимит памяти процесса. - person Ivan Bartsov; 08.05.2014
comment
@hackbod Нужно ли также обнулять ссылки View фрагмента? Я имею в виду, если фрагмент уничтожен (вызывается onDestroyView()) и GC хочет собрать мусор, будут ли собираться старые представления фрагмента, если ссылки на представления останутся во фрагменте (утечка памяти)? Или это не имеет значения, потому что если фрагмент уничтожен, то все объекты, удерживаемые фрагментом (только фрагментом), могут быть собраны сборщиком мусора? - person traninho; 05.11.2014
comment
@traninho Хорошая мысль. При помещении в задний стек экземпляр Fragment не уничтожается, поэтому onDestroyView() всегда должен обнулять любые имеющиеся у него ссылки на представления. В противном случае это не позволит мертвым представлениям получить GCed. Кроме того, сохраненные фрагменты без обратного стека (см. setRetainInstance(true)) не уничтожаются даже при воссоздании Activity, поэтому, сохраняя ссылки на представление, вы можете утечь весь Activity. Обычно этого не происходит, потому что в 99% случаев ссылки на представления обновляются в onCreateView(), который действительно вызывается при воссоздании Actvity, поэтому после этого ссылки все равно указывают на новые представления. - person Ivan Bartsov; 07.11.2014
comment
@IvanBartsov Спасибо за ответ. В настоящее время я борюсь с OOM в своем приложении. Для простоты у меня есть 1 действие (A) и один фрагмент (F), который содержит просмотр пейджера с двумя другими фрагментами (F1 и F2). Из F1, если пользователь нажимает кнопку, другой экземпляр (F) открывается из A и добавляется в стопку. Так что если пользователь сделает это несколько раз, то рано или поздно произойдет OOM. Я попытаюсь обнулить каждую ссылку на представление в onDestroy(). - person traninho; 07.11.2014
comment
@traninho Что ж, подобные вещи нелегко диагностировать без кода. Мы должны передать это в чат, если вам нужна дополнительная помощь. - person Ivan Bartsov; 15.12.2014
comment
@IvanBartsov Нет, не надо, мне просто было любопытно. Но все равно спасибо! :) - person traninho; 05.01.2015
comment
Лучший ответ, контрац! - person Natan Lotério; 01.10.2015
comment
@IvanBartsov Думаю, мне здесь не хватает одного момента. Объект FragmentState сохраняется только при изменении конфигурации, а не соответствующий фактический фрагмент. Но объект FragmentState содержит ссылку (mInstance) на свой фрагмент, и это сильная ссылка. Итак, как GC удается уничтожить фрагмент, не уничтожая его объект состояния, который включает ссылку на его фрагмент. - person stdout; 26.05.2017

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

Мне пришлось добавить невероятное количество фрагментов (более сотни) в цикл for, чтобы произошло OutOfMemoryError, но это произошло. И проверив свои журналы, я увидел, что методы onCreate() и onCreateView() вызывались много раз, а onSaveInstance(), onPause() и onDestroy вообще не вызывались.

Для справки, вот как я добавил фрагменты в стопку:

getSupportFragmentManager().beginTransaction().add(R.id.scene_fragment_container, mSceneFragment).addToBackStack("FOOBAR").commit();

Фрагменты, которые я добавил, были довольно простыми: ImageView, EditText, пара TextViews, SeekBar и ListView.

Но если вы не храните в памяти огромное количество данных, это не должно быть проблемой.

Позже я попытался добавить только 50 в задний стек, уничтожив приложение и перезапустив его. И, как я надеялся/догадывался, все фрагменты были восстановлены (и вызваны методы onSaveInstance() и onPause()), поэтому моя реализация жизненного цикла не была проблемой, вызвавшей срабатывание OutOfMemoryError.

person Beowulf Bjornson    schedule 07.02.2012

Из developer.android.com/guide/topics/fundamentals/fragments.html.

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

person Bill Gary    schedule 13.12.2011
comment
Спасибо Билл. Тем не менее, это так или иначе не говорит о том, сохраняются ли фрагменты в заднем стеке действий в памяти или освобождаются по мере необходимости, и это то, что мне интересно... - person cottonBallPaws; 13.12.2011
comment
@littleFluffyKitty, почему это не отвечает на твой вопрос? Очевидно, что они находятся в памяти, но приостановлены, как и обычная активность, они не очищаются, пока вы не вернетесь назад или ваша активность не будет уничтожена. - person Warpzit; 08.02.2012

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

person Sid    schedule 10.02.2012