Управление жизненным циклом Android фрагментов в ViewPager и FragmentPagerAdapter

Я изо всех сил пытался выяснить, что такое правильное управление фрагментами в FragmentActivity с ViewPager. Прежде чем я углублюсь в детали, краткое изложение проблемы, с которой я столкнулся, заключается в следующем:

У меня есть FragmentActivity с ViewPager. В ViewPager используется нестандартный, но очень простой FragmentPagerAdapter. Каждый Fragment в ViewPager состоит из ExpandableListView. У меня также есть кнопка панели действий под названием «Обновить». Пока предположим, что ViewPager имеет только один Fragment. Активность создана, и ExpandableListView Fragment заполняется (пока все хорошо). При нажатии кнопки «Обновить» метод обработки в FragmentActivity перебирает список Fragments, назначенных FragmentPagerAdapter, и вызывает refresh() для каждого Fragment для заполнения его ListView. Однако при изменении ориентации устройства (например, с книжной на альбомную) активность создается заново, как и фрагменты. При нажатии кнопки «Обновить» теперь будет выполняться итерация по неинициализированным Fragments.

Я знаю, что выражаюсь довольно расплывчато, особенно без примера кода, но, пожалуйста, потерпите меня. Я проследил проблему и вызовы методов с самого начала приложения/действия следующим образом:

  1. FragmentActivity.onCreate()
  2. FragmentActivity.setContentView()
  3. FragmentActivity.createPagerFragments() ‹-- это создает ArrayList фрагментов и назначает их новому FragmentPagerAdapter, который, в свою очередь, назначается ViewPager.
  4. Fragment.onAttach()
  5. Fragment.onCreate() ‹-- здесь ничего особенного, просто вызов суперметода.
  6. Fragment.onCreateView()‹-- тут тоже ничего особенного, просто раздувание макета
  7. Fragment.onActivityCreated() ‹-- здесь тоже ничего.
  8. ‹‹ Все хорошо, ориентация меняется здесь >>
  9. FragmentActivity.onCreate()
  10. Fragment.onAttach()
  11. Fragment.onCreate()
  12. FragmentActivity.setContentView()
  13. FragmentActivity.createPagerFragments()
  14. Fragment.onCreateView()
  15. Fragment.onActivityCreated()
  16. ‹‹ Нажата кнопка «Обновить» >>
  17. FragmentActivity.refresh() ‹-- перебирает только что созданные фрагменты из # 13 (не для Android!).
  18. ‹‹ Сбой: исключение NullPointerException для mExpandableListView во фрагменте. >>

Итак, проблема, как я ее вижу, заключается в следующем: когда Android воссоздает FragmentActivity и его Views после изменения ориентации экрана (вызовы 9-15 выше), он создает новые объекты Fragment, состояние которых восстанавливается до того состояния, в котором они находились. оригинальные были. Однако похоже, что ими полностью управляет FragmentManager, а не FragmentPagerAdapter. Напротив, когда FragmentPagerAdapter воссоздается вместе с Fragments в методе onCreate активности (см. вызов № 13), Fragments, которые назначаются адаптеру, никогда не вызывают свои методы Fragment.onCreate() или Fragment.onCreateView(). Поэтому, когда вызывается метод refresh() (см. #17), метод перебирает эти Fragments, которые не были инициализированы. Поэтому, когда они пытаются заполнить ExpandableListView, переменная экземпляра представления будет NULL. Этого следовало ожидать, поскольку переменная экземпляра назначается только в методе Fragment.onCreateView(), который никогда не вызывается для этих Fragments.

Итак, мой вопрос: как правильно повторно использовать воссозданные (Android) Fragments после изменения ориентации экрана, чтобы избежать создания новых, которые не инициализируются? Мне нужна действующая ссылка на них, чтобы вызвать их метод refresh(), который заполняет их по запросу. В идеале они также должны быть назначены на FragmentPagerAdapter.

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

Благодарю вас!


person vmivanov    schedule 03.12.2013    source источник
comment
Привет! У меня такая же проблема, как вы ее исправили, потому что ответ ниже не очень полезен)   -  person yozhik    schedule 25.04.2018


Ответы (3)


Это много читать, но после прочтения только введения и вопроса и опыта работы с FragmentStatePagerAdapter, который похож на FragmentPagerAdapter, я могу сказать вам, что:

После поворота ваш адаптер АВТОМАГИЧЕСКИ прикрепит старые фрагменты. Таким образом, кажется, что, хотя адаптер, создающий активность, воссоздается, FragmentManager, который является глобальным, и его воссоздание активности сохранения экземпляра обнаружит, что новый FragmentStatePagerAdapter объединен с тем же ViewPager и запрашивает те же фрагменты, и просто извлекает их из BackStack фрагмента.

Вы, как разработчик фрагментов, можете заметить это поведение, продолжая вызывать Fragment.onAttach() и Fragment.onDetach(). Когда происходит onAttach(), это либо создание вашего фрагмента, либо его повторное использование после поворота. Вы должны быть в состоянии отличить, что Фрагмент был повернут с использованием обратного вызова onRestoreRnstanceState().

Вы увидите в своих журналах множество журналов onCreate() и других состояний одновременно, потому что FragmentStatePagerAdapter всегда извлекает/создает минимум 3 фрагмента (кроме случаев, когда вы устанавливаете, что их только 2 или 1), поэтому также после поворота экрана 3 фрагмента будут повторно прикреплены из задним ходом

Я надеюсь, что это помогло.

person Malachiasz    schedule 17.02.2014

Я считаю, что этот вопрос о получении текущего фрагмента из ViewPager будет помочь тебе. Как уже отмечалось, фрагменты управляются Fragment(State)PagerAdapter, а НЕ жизненным циклом Activity или Fragment.

  • При первом создании активности фрагменты возвращаются методом getItem. Этот метод вызывается только один раз для каждого фрагмента, даже если активность воссоздается.
  • В последующие разы фрагменты возвращаются методом instantiateItem. Скорее всего, это то место, где вам нужно получить свои фрагменты и вызвать их методы обновления.
person kgiannakakis    schedule 13.06.2014

Как насчет добавления этого в тег действия в манифесте:

android:configChanges="orientation"

или это для API 13 или выше

android:configChanges="orientation|screenSize" 

поэтому он не будет воссоздавать ваши фрагменты при изменении ориентации.

person Dark Leonhart    schedule 27.01.2014
comment
Этого лучше избегать. Среди прочего, это не позволит вам иметь отдельный макет для альбомной и портретной ориентаций (из XML). Кроме того, это просто прячет проблему под ковер. Аналогичная ситуация может возникнуть, когда действие уничтожается из-за нехватки памяти, а затем создается заново. - person kgiannakakis; 13.06.2014