Вложенные фрагменты сохраняются при повороте экрана

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

public class ParentFragment extends BaseFragment
{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return  inflater.inflate(R.layout.fragment_parent, container);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

         getChildFragmentManager()
                 .beginTransaction()
                 .add(getId(), new ParentFragmentChild(), ParentFragmentChild.class.getName())
                 .commit();
    }

    @Override
    public void onResume() {
        super.onResume();
        log.verbose("onResume(), numChildFragments: " + getChildFragmentManager().getFragments().size());
    }     
}

public class ParentFragmentChild extends BaseFragment
{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_child, null);
    }
}

BaseFragment просто регистрирует вызовы методов. Вот что я вижу, когда поворачиваю экран.

Когда сначала появляется активность

ParentFragment﹕ onAttach(): ParentFragment{420d0a98 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420d08d0 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 1
ParentFragmentChild﹕ onResume()

Поворот экрана №1

ParentFragmentChild﹕ onPause()
ParentFragment﹕ onPause()
ParentFragment﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onStop()
ParentFragment﹕ onStop()
ParentFragmentChild﹕ onDestroyView()
ParentFragment﹕ onDestroyView()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragment﹕ onDestroy()
ParentFragment﹕ onDetach()
ParentFragment﹕ onAttach(): ParentFragment{4211bc38 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420f4180 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{42132a08 #1 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 2
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()

Поворот экрана №2

ParentFragmentChild﹕ onPause()
ParentFragmentChild﹕ onPause()
ParentFragment﹕ onPause()
ParentFragment﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onStop()
ParentFragmentChild﹕ onStop()
ParentFragment﹕ onStop()
ParentFragmentChild﹕ onDestroyView()
ParentFragmentChild﹕ onDestroyView()
ParentFragment﹕ onDestroyView()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragment﹕ onDestroy()
ParentFragment﹕ onDetach()
ParentFragment﹕ onAttach(): ParentFragment{42122a48 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420ffd48 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420fffa0 #1 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{42101488 #2 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 3
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()

Они продолжают множиться. Кто-нибудь знает, почему так?


person ievgen    schedule 11.06.2014    source источник
comment
Я понятия не имею, но однажды я столкнулся с той же ситуацией с вложенными фрагментами, и у меня возникла ситуация, когда установка myFragment.retainOnInstance(true) фактически привела к «умиранию» фрагмента и наоборот. Хотя точного сценария не помню.   -  person fweigl    schedule 12.06.2014
comment
вы поместили ParentFragment в backstack   -  person Rod_Algonquin    schedule 12.06.2014
comment
я ничего особенного не делал   -  person ievgen    schedule 13.06.2014
comment
@Ascorbin Большое спасибо, брууух. 2,5 года спустя... У меня есть фрагмент x с ViewPager, и, конечно же, ViewPager тоже имеет дочерние фрагменты. При ротации дочерние элементы ViewPager теряли свои свойства. Я так запутался, почему родительский фрагмент x в порядке, а его дети - нет. Прошли дни отладки и чтения SO для этой проблемы, и установка RetainInstance = true для родительского фрагмента x (тот, на котором размещен ViewPager) заставила его работать.   -  person Konayuki    schedule 23.02.2017


Ответы (1)


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

Они выживают по той же причине, что и ParentFragment, даже если вы не сохраняете экземпляр с setRetainInstance(). Этой причиной является FragmentManager, в данном случае ChildFragmentManager ParentFragment использует для обработки вложенных фрагментов.

Некоторые вещи, которые вам нужно знать:

  • FragmentManager отвечает за управление фрагментами и добавление их в иерархию представлений активности.
  • The FragmentManager handles two things:
    • A list of fragments (remember this!).
    • задний стек фрагментных транзакций.
  • Чтобы добавить, удалить, присоединить, отсоединить или заменить фрагменты в списке фрагментов вы используете Fragment Transactions.
  • Когда вы запрашиваете у FragmentManager фрагмент, используя идентификатор представления контейнера (findFragmentById), если фрагмент уже есть в списке, FragmentManager вернет его. Тогда вы можете использовать его.

При первом появлении активности

Поскольку приложение только что запущено, у вас есть только один экземпляр каждого фрагмента. Один из ParentFragment и один из ParentFragmentChild. Все идет нормально.

Поворот экрана №1

В этот момент Activity FragmentManager и ParentFragment ChildFragmentManager сохраняют свой список фрагментов. Затем вы можете увидеть, как оба фрагмента полностью разрушены.

Когда Activity воссоздается, новые FragmentManagers извлекают список и воссоздают перечисленные фрагменты, чтобы все было так, как было до изменения ориентации. Обратите внимание, что это не одни и те же экземпляры, это новые фрагменты, воссозданные Android (поэтому у вас не может быть фрагмента без пустого конструктора, он нужен Android для воссоздания фрагментов).

Теперь это код внутри вашего ParentFragment:

getChildFragmentManager()
             .beginTransaction()
             .add(getId(), new ParentFragmentChild(), ParentFragmentChild.class.getName())
             .commit();

Вы не пытаетесь выяснить, есть ли в списке ChildFragmentManager уже ParentFragmentChild (который уже есть). По этой причине вы можете видеть два создаваемых фрагмента, первый воссоздается ChildFragmentManager, второй создается предыдущим кодом.

Поворот экрана №2

До второго изменения ориентации в списке ChildFragmentManager было два ParentChildFragment, они были пересозданы и еще один создан кодом.

Лучше использовать фрагменты, воссозданные FragmentManager, чем создавать новые.

person ILovemyPoncho    schedule 12.06.2014
comment
Вы говорите, что менеджеры фрагментов хранят статический список фрагментов? И почему тогда ParentFragment не выживает? - person ievgen; 13.06.2014