В этом руководстве рассказывается об инструментах, которые можно использовать для отладки фрагментов.
Ведение журнала FragmentManager
FragmentManager может отправлять в Logcat различные сообщения. По умолчанию эта функция отключена, но иногда такие сообщения в журнале могут помочь вам устранить неполадки с фрагментами. FragmentManager выдает наиболее значимые результаты на уровнях ведения журнала DEBUG и VERBOSE.
Чтобы включить ведение журнала, используйте следующую команду adb shell:
adb shell setprop log.tag.FragmentManager DEBUG
Вы также можете включить подробное ведение журналов следующим образом:
adb shell setprop log.tag.FragmentManager VERBOSE
Если вы включите подробное ведение журнала, то сможете применить фильтр уровня журнала в окне Logcat. Однако в этом случае будут отфильтрованы все журналы, а не только журналы FragmentManager. Рекомендуем включать ведение журналов FragmentManager только на нужном уровне.
Ведение журнала отладки
На уровне DEBUG FragmentManager обычно выдает сообщения в журнале, связанные с изменениями состояния жизненного цикла. Каждая запись в журнале содержит дамп toString() из Fragment.
Запись в журнале содержит следующую информацию:
- Простое название класса экземпляра
Fragment. - Хеш-код идентификатора экземпляра
Fragment. - Уникальный идентификатор экземпляра
Fragment, присвоенный менеджером фрагментов. Это значение не меняется при изменении конфигурации, а также при завершении и повторном создании процесса. - Идентификатор контейнера, в который добавлен объект
Fragment(если задан). - Тег
Fragment, но только если он задан.
Ниже приведен пример записи в журнале DEBUG.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
- Класс
Fragment–NavHostFragment. - Хеш-код идентификатора:
92d8f1d. - Уникальный идентификатор:
fd92599e-c349-4660-b2d6-0ece9ec72f7b. - Идентификатор контейнера:
0x7f080116. - Тег не указан, так как он не был задан. Если он есть, то указывается после идентификатора в формате
tag=tag_value.
Для краткости и удобства чтения UUID в следующих примерах сокращены.
Ниже показано, как инициализируется NavHostFragment, а затем создается startDestination
Fragment типа FirstFragment и переходит в состояние RESUMED:
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: mName=null mIndex=-1 mCommitted=false
D/FragmentManager: Operations:
D/FragmentManager: Op #0: SET_PRIMARY_NAV NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: mName=null mIndex=-1 mCommitted=false
D/FragmentManager: Operations:
D/FragmentManager: Op #0: REPLACE FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: Op #1: SET_PRIMARY_NAV FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
После взаимодействия с пользователем FirstFragment переходит из одного состояния жизненного цикла в другое. Затем создается экземпляр SecondFragment, который переходит в состояние RESUMED:
D/FragmentManager: mName=07c8a5e8-54a3-4e21-b2cc-c8efc37c4cf5 mIndex=-1 mCommitted=false
D/FragmentManager: Operations:
D/FragmentManager: Op #0: REPLACE SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: Op #1: SET_PRIMARY_NAV SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: SecondFragment{84132db} (<UUID> id=0x7f080116)
Все экземпляры Fragment имеют суффикс с идентификатором, чтобы вы могли отслеживать разные экземпляры одного и того же класса Fragment.
Подробное ведение журнала
На уровне VERBOSE FragmentManager обычно выдает сообщения в журнале о своем внутреннем состоянии:
V/FragmentManager: Run: BackStackEntry{f9d3ff3}
V/FragmentManager: add: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{5cfd2ae}
D/FragmentManager: mName=null mIndex=-1 mCommitted=false
D/FragmentManager: Operations:
D/FragmentManager: Op #0: SET_PRIMARY_NAV NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{e93833f}
D/FragmentManager: mName=null mIndex=-1 mCommitted=false
D/FragmentManager: Operations:
D/FragmentManager: Op #0: REPLACE FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: Op #1: SET_PRIMARY_NAV FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{e93833f}
V/FragmentManager: add: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment FirstFragment{886440c} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{7578ffa V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.constraintlayout.widget.ConstraintLayout{3968808 I.E...... ......I. 0,0-0,0} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{2ba8ba1 V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.fragment.app.FragmentContainerView{7578ffa I.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{5cfd2ae}
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
В этом примере рассматривается только загрузка на FirstFragment. Если включить переход на SecondFragment, количество записей в журнале значительно увеличится.
Многие сообщения в журнале уровня VERBOSE не представляют интереса для разработчиков приложений. Однако, видя, когда происходят изменения в обратном стеке, можно отлаживать некоторые проблемы.
StrictMode для фрагментов
В библиотеке Jetpack Fragment версии 1.4.0 и выше есть StrictMode для фрагментов. Он может выявить некоторые распространенные проблемы, которые могут привести к неожиданному поведению вашего приложения. Дополнительную информацию о работе с StrictMode можно найти в статье StrictMode.
Специальный Policy определяет, какие нарушения обнаруживаются и какие штрафы применяются при их обнаружении.
Чтобы применить специальное правило StrictMode, назначьте его FragmentManager.
Сделайте это как можно раньше. В этом случае вы делаете это в блоке init или в конструкторе Java:
Kotlin
class ExampleActivity : AppCompatActivity() { init { supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java) .build() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) ... } }
Java
class ExampleActivity extends AppCompatActivity() { ExampleActivity() { getSupportFragmentManager().setStrictModePolicy( new FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment.class, FragmentReuseViolation.class) .build() ); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState) ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); ... } }
Если вам нужно знать Context, чтобы определить, следует ли включить StrictMode (например, по значению логического ресурса), вы можете отложить назначение правила StrictMode для FragmentManager с помощью OnContextAvailableListener:
Kotlin
class ExampleActivity : AppCompatActivity() { init { addOnContextAvailableListener { context -> if(context.resources.getBoolean(R.bool.enable_strict_mode)) { supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java) .build() } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) ... } }
Java
class ExampleActivity extends AppCompatActivity() { ExampleActivity() { addOnContextAvailableListener((context) -> { if(context.getResources().getBoolean(R.bool.enable_strict_mode)) { getSupportFragmentManager().setStrictModePolicy( new FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment.class, FragmentReuseViolation.class) .build() ); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState) ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); ... } }
Последний момент, когда вы можете настроить StrictMode для отслеживания всех возможных нарушений, – в onCreate() перед вызовом super.onCreate():
Kotlin
class ExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java) .build() super.onCreate(savedInstanceState) val binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) ... } }
Java
class ExampleActivity extends AppCompatActivity() { @Override protected void onCreate(Bundle savedInstanceState) { getSupportFragmentManager().setStrictModePolicy( new FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() .allowViolation(FirstFragment.class, FragmentReuseViolation.class) .build() ); super.onCreate(savedInstanceState) ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); ... } }
Правило, используемое в этих примерах, обнаруживает только нарушения, связанные с повторным использованием фрагментов, и приложение завершает работу при каждом их возникновении. Метод penaltyDeath() может быть полезен в отладочных сборках, поскольку он быстро завершает работу приложения, не позволяя игнорировать нарушения.
Вы также можете выборочно разрешить определенные нарушения. Однако правило, использованное в предыдущем примере, обеспечивает соблюдение этого нарушения для всех остальных типов фрагментов. Это полезно, когда компонент сторонней библиотеки может содержать нарушения StrictMode.
В таких случаях вы можете временно добавить эти нарушения в белый список StrictMode для компонентов, которыми вы не владеете, пока библиотека не исправит их нарушение.
Инструкции по настройке других нарушений можно найти в документации по FragmentStrictMode.Policy.Builder.
Существует три типа штрафов.
penaltyLog()выводит сведения о нарушениях в Logcat.penaltyDeath()завершает работу приложения при обнаружении нарушений.penaltyListener()позволяет добавить собственный прослушиватель, который вызывается при обнаружении нарушений.
Вы можете применять любые комбинации штрафов в Policy. Если в правиле не указан штраф, по умолчанию применяется значение penaltyLog(). Если вы примените штраф, отличный от penaltyLog(), в пользовательском Policy, то penaltyLog() будет отключен, если вы не установите его явным образом.
penaltyListener() может быть полезен, если у вас есть сторонняя библиотека ведения журналов, в которую вы хотите записывать нарушения. Вы также можете включить отслеживание некритических нарушений в конечных сборках и регистрировать их в библиотеке отчетов о сбоях. Такой подход позволяет выявлять нарушения, которые иначе могли бы остаться незамеченными.
Чтобы задать глобальное правило StrictMode, установите правило по умолчанию, которое будет применяться ко всем экземплярам FragmentManager, с помощью метода FragmentStrictMode.setDefaultPolicy():
Kotlin
class MyApplication : Application() { override fun onCreate() { super.onCreate() FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder() .detectFragmentReuse() .detectFragmentTagUsage() .detectRetainInstanceUsage() .detectSetUserVisibleHint() .detectTargetFragmentUsage() .detectWrongFragmentContainer() .apply { if (BuildConfig.DEBUG) { // Fail early on DEBUG builds penaltyDeath() } else { // Log to Crashlytics on RELEASE builds penaltyListener { FirebaseCrashlytics.getInstance().recordException(it) } } } .build() } }
Java
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); FragmentStrictMode.Policy.Builder builder = new FragmentStrictMode.Policy.Builder(); builder.detectFragmentReuse() .detectFragmentTagUsage() .detectRetainInstanceUsage() .detectSetUserVisibleHint() .detectTargetFragmentUsage() .detectWrongFragmentContainer(); if (BuildConfig.DEBUG) { // Fail early on DEBUG builds builder.penaltyDeath(); } else { // Log to Crashlytics on RELEASE builds builder.penaltyListener((exception) -> FirebaseCrashlytics.getInstance().recordException(exception) ); } FragmentStrictMode.setDefaultPolicy(builder.build()); } }
В разделах ниже описаны типы нарушений и возможные способы их устранения.
Повторное использование фрагмента
Нарушение, связанное с повторным использованием фрагмента, включается с помощью detectFragmentReuse() и вызывает FragmentReuseViolation.
Это нарушение указывает на повторное использование экземпляра Fragment после его удаления из FragmentManager. Это может привести к проблемам, поскольку Fragment может сохранить состояние предыдущего использования и работать некорректно. Если вы будете каждый раз создавать новый экземпляр, то при добавлении в FragmentManager он всегда будет находиться в исходном состоянии.
Использование тега фрагмента
Нарушение, связанное с использованием тега fragment, включается с помощью detectFragmentTagUsage() и вызывает FragmentTagUsageViolation.
Это нарушение означает, что Fragment заполняется с помощью тега <fragment> в макете XML. Чтобы устранить эту проблему, разверните Fragment внутри <androidx.fragment.app.FragmentContainerView>, а не в теге <fragment>. Фрагменты, развернутые с помощью FragmentContainerView, могут надежно обрабатывать транзакции Fragment и изменения конфигурации. Если вы используете тег <fragment>, они могут работать некорректно.
Сохранить использование экземпляра
Нарушение, связанное с использованием сохраняемого экземпляра, включается с помощью detectRetainInstanceUsage() и вызывает RetainInstanceUsageViolation.
Это нарушение указывает на использование сохраненного Fragment, в частности, если есть вызовы setRetainInstance() или getRetainInstance(), которые устарели.
Вместо того чтобы использовать эти методы для самостоятельного управления сохраненными экземплярами Fragment, сохраняйте состояние в ViewModel, который сделает это за вас.
Задать подсказку, видимую пользователю
Нарушение, связанное с методом setUserVisibleHint, включается с помощью detectSetUserVisibleHint() и вызывает SetUserVisibleHintViolation.
Это нарушение указывает на вызов setUserVisibleHint(), который больше не поддерживается.
Если вы вызываете этот метод вручную, вместо него вызовите setMaxLifecycle(). Если вы переопределите этот метод, перенесите поведение в onResume() при передаче true и в onPause() при передаче false.
Использование целевых фрагментов
Нарушение, связанное с использованием целевого фрагмента, включается с помощью detectTargetFragmentUsage() и вызывает TargetFragmentUsageViolation.
Это нарушение указывает на вызов setTargetFragment(), getTargetFragment() или getTargetRequestCode(). Все эти методы устарели. Вместо этого зарегистрируйте FragmentResultListener. Дополнительную информацию см. в статье Передача результатов между фрагментами.
Неверный контейнер фрагмента
Нарушение, связанное с неправильным контейнером фрагмента, включается с помощью detectWrongFragmentContainer() и вызывает WrongFragmentContainerViolation.
Это нарушение означает, что Fragment добавлен в контейнер, отличный от FragmentContainerView. Как и в случае с тегом Fragment, транзакции фрагментов могут работать некорректно, если они не размещены внутри FragmentContainerView. Использование представления контейнера также помогает решить проблему в API View, из-за которой фрагменты, использующие анимацию выхода, рисуются поверх всех остальных фрагментов.