Экземпляр фрагмента сохраняется, но дочерний фрагмент не повторно прикреплен

Обновление: принятые ответы на вопросы объяснения (ошибка) с обходом, но также см. Мою работу на основе Kotlin, прилагаемую в качестве ответа ниже.

Этот код находится в Kotlin, но я думаю, что это основной цикл жизненного цикла Android.

У меня есть фрагмент, который содержит ссылку на другой «субфрагмент»,

Вот в основном то, что я делаю:

  1. У меня есть основной фрагмент, у которого retainInstance установлено значение true
  2. У меня есть поле в основном фрагменте, который будет содержать ссылку на подфрагмент, изначально это поле имеет значение null
  3. В onCreateView основного фрагмента я проверяю, является ли поле подфрагмента нулевым, если это так, я создаю экземпляр субфрагмента и назначаю его в поле
  4. Наконец, я добавляю подфрагмент в контейнер в макете основного фрагмента.
  5. Если поле не является нулевым, то есть мы находимся в onCreateView из-за изменения конфигурации, я не воссоздаю подфрагмент, я просто пытаюсь добавить его в контейнер.

Когда устройство повернуто, я наблюдаю onPaused() и onDestroyView() , но я не вижу, чтобы какие-либо методы lifecyle вызывались на подфрагменте во время добавления сохраненной ссылки на подфрагмент, к child_container, когда воссоздается представление основных фрагментов.

Сетевой аффект заключается в том, что я не вижу вид субфрагмента в основном фрагменте. Если я прокомментирую if (subfragment == null) и просто создаю новый подфрагмент каждый раз, я вижу подфрагмент в представлении.

Обновить

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

Я добавил код в действие по onWindowFocusChanged и я вижу что-то подобное при первом запуске приложения:

 activity is in view fm = FragmentManager{b13b9b18 in Tab1Fragment{b13b2b98}} tab 1 fragments = [DefaultSubfragment{b13bb610 #0 id=0x7f0c0078}] 

а затем после вращения:

 activity is in view fm = FragmentManager{b13f9c30 in Tab1Fragment{b13b2b98}} tab 1 fragments = null 

здесь fm является childFragmentManager, и, как вы можете видеть, у нас все еще есть тот же экземпляр Tab1Fragment, но у него есть новый childFragmentManager, который, как мне кажется, нежелателен, и из-за ошибки, о которой сообщается в ответе ниже. Дело в том, что я добавил субфрагмент к этому новому childFragmentManger. Таким образом, похоже, что транзакция никогда не выполняется со ссылкой на фрагмент, который был сохранен, но завершается, если я создаю новый фрагмент. (Я попытался вызвать executePendingTransactions для нового childFragmentManager)


 class Tab1Fragment: Fragment() { var subfragment: DefaultSubfragment? = null override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val rootView = inflater!!.inflate(R.layout.fragment_main, container, false) if (subfragment == null ) { subfragment = DefaultSubfragment() subfragment!!.sectionLabel = "label 1" subfragment!!.buttonText = "button 1" } addRootContentToContainer(R.id.child_container, content = subfragment!!) return rootView } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) { val transaction = childFragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.commit() } 

Ваша проблема похожа на проблему, описанную здесь:

https://code.google.com/p/android/issues/detail?id=74222

к сожалению, эта проблема, вероятно, не будет устранена Google.

Использование сохраненных фрагментов для пользовательского интерфейса или вложенных фрагментов не является хорошей идеей – они рекомендуются для использования вместо onRetainNonConfigurationInstance, т. Е. для больших коллекций / структур данных. Также вы можете найти Loaders лучше, чем сохраненные фрагменты, они также сохраняются во время изменений конфигурации.

Кстати. Я считаю, что android:configChanges фрагменты больше взлома – например, с помощью android:configChanges для «исправления» проблем, вызванных вращением экрана. Все работает, пока пользователь не нажимает на главный экран, и андроид решает убить ваш процесс приложения. Как только пользователь захочет вернуться к вашему приложению – ваши сохраненные фрагменты будут уничтожены – и вам все равно придется его воссоздать. Поэтому всегда лучше кодировать все, как если бы ваши ресурсы могли быть уничтожены в любое время.

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

Одна из должностей обеспечивает работу (которая, кажется, работает хорошо). Работа вокруг предполагает создание подкласса Fragment и использование отражения.

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

Как это работает:

  1. В вашем фрагменте, который будет содержать дочерние фрагменты, вы создадите поле keepedChildFragmentManager, которое будет удерживать childFragmentManager, который будет потерян во время изменения конфигурации
  2. В обратном вызове onCreate для того же фрагмента вы устанавливаете значение keepInstance равным true
  3. В обратном вызове onAttach для одного и того же фрагмента вы проверяете, не является ли keepedChildFragmentManger недействительным, если вы вызываете функцию расширения фрагмента, которая повторно присоединяет keepedChildFragmentManager, в противном случае вы установите keepedChildFragmentManager текущему childFragmentManager.
  4. Наконец, вам нужно исправить дочерние фрагменты, чтобы указать на вновь созданный хостинг (ошибка оставляет их ссылкой на старую активность, что, я думаю, приводит к утечке памяти).

Вот пример:

Расширения фрагментов Kotlin

 // some convenience functions inline fun Fragment.pushContentIntoContainer(containerId: Int, content: Fragment) { val transaction = fragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.addToBackStack("tag") transaction.commit() } inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) { val transaction = childFragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.commit() } // here we address the bug inline fun Fragment.reattachRetainedChildFragmentManager(childFragmentManager: FragmentManager) { setChildFragmentManager(childFragmentManager) updateChildFragmentsHost() } fun Fragment.setChildFragmentManager(childFragmentManager: FragmentManager) { if (childFragmentManager is FragmentManagerImpl) { mChildFragmentManager = childFragmentManager // mChildFragmentManager is private to Fragment, but the extension can touch it } } fun Fragment.updateChildFragmentsHost() { mChildFragmentManager.fragments.forEach { fragment -> // fragments is hidden in Fragment fragment?.mHost = mHost // mHost is private also } } 

Фрагмент Хостинг ребенка Фрагменты

 class Tab1Fragment : Fragment() , TabRootFragment { var subfragment: DefaultSubfragment? = null var retainedChildFragmentManager: FragmentManager? = null override val title = "Tab 1" override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val rootView = inflater!!.inflate(R.layout.fragment_main, container, false) if (subfragment == null ) { subfragment = DefaultSubfragment() subfragment!!.sectionLable = "label 1x" subfragment!!.buttonText = "button 1" addRootContentToContainer(R.id.child_container, content = subfragment!!) } return rootView } override fun onAttach(context: Context?) { super.onAttach(context) if (retainedChildFragmentManager != null) { reattachRetainedChildFragmentManager(retainedChildFragmentManager!!) } else { retainedChildFragmentManager = childFragmentManager } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } } 
Intereting Posts
Ошибка android-kotlin: неразрешенные ссылки привязки данных и ActivityMainBinding Ошибка при обработке аннотаций с помощью Kapt и Dagger2 Что означает «им» в Котлине? Привязка данных Android к Kotlin, BaseObservable и пользовательский делегат Kotlin неожиданная `неразрешенная ссылка` Как получить Kotlin KClass из строки имени класса пакета? Ошибка при смене лямбды для плоской карты из двух наблюдений на плоскую карту двух синглов Ошибка «нет основного манифеста» в intellij при выполнении jar Фильтрующий лямбда-фильтр Kotlin с использованием индекса итерации Программировать триггерную кнопку HTML в Java Межстраничные объявления Admob: «IllegalStateException: только действия в полноэкранном режиме могут запрашивать ориентацию» Приложение Kotlin cli не работает после обновления к kotlin 1.0.0-beta ProcessBuilder не может найти Kotlin в пути Ошибка раздувания фрагмента класса android kotlin Как отладить ошибку «Не удалось разрешить ошибку com.google.android:android» в Anko