Отладка ваших фрагментов,Отладка ваших фрагментов

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

Ведение журнала 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)
  • Класс FragmentNavHostFragment .
  • Идентификационный хеш-код — 92d8f1d .
  • Уникальный идентификатор: fd92599e-c349-4660-b2d6-0ece9ec72f7b .
  • Идентификатор контейнера — 0x7f080116 .
  • Тег опущен, поскольку он не был установлен. Если он присутствует, он следует за идентификатором в формате tag=tag_value .

Для краткости и удобства чтения UUID в следующих примерах сокращены.

Вот инициализируется NavHostFragment , а затем создается Fragment startDestination типа 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 для фрагментов

Версия 1.4.0 и выше библиотеки Jetpack Fragment включает StrictMode для фрагментов. Он может обнаружить некоторые распространенные проблемы, которые могут привести к неожиданному поведению вашего приложения. Дополнительные сведения о работе со StrictMode см. в разделе StrictMode .

Пользовательская Policy определяет, какие нарушения обнаруживаются, и определяет, какое наказание применяется при обнаружении нарушений.

Чтобы применить пользовательскую политику StrictMode, назначьте ее FragmentManager . Сделайте это как можно раньше. В этом случае вы делаете это в блоке init или в конструкторе Java:

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)
       
...
   
}
}
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 :

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)
       
...
   
}
}
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() :

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)
       
...
   
}
}
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() . Если вы применяете в своей пользовательской Policy штраф, отличный от penaltyLog() , то penaltyLog() отключается, если вы не установили его явно.

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

Чтобы установить глобальную политику StrictMode, установите политику по умолчанию, которая применяется ко всем экземплярам FragmentManager , с помощью метода FragmentStrictMode.setDefaultPolicy() :

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()
   
}
}
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 .

Использование тега фрагмента

Нарушение использования тега фрагмента включается с помощью detectFragmentTagUsage() и выдает FragmentTagUsageViolation .

Это нарушение указывает на то, что Fragment раздувается с помощью тега <fragment> в макете XML. Чтобы решить эту проблему, раздуйте свой Fragment внутри <androidx.fragment.app.FragmentContainerView> а не в теге <fragment> . Фрагменты, раздутые с помощью FragmentContainerView могут надежно обрабатывать транзакции Fragment и изменения конфигурации. Они могут работать не так, как ожидалось, если вместо этого вы используете тег <fragment> .

Сохранять использование экземпляра

Нарушение использования экземпляра сохранения включается с помощью detectRetainInstanceUsage() и выдает RetainInstanceUsageViolation .

Это нарушение указывает на использование сохраненного Fragment , в частности, если есть вызовы setRetainInstance() или getRetainInstance() , которые оба устарели.

Вместо того, чтобы использовать эти методы для самостоятельного управления сохраненными экземплярами Fragment , сохраните состояние в ViewModel , который сделает это за вас.

Установить видимую пользователю подсказку

Нарушение подсказки, установленной пользователем, включается с помощью detectSetUserVisibleHint() и выдает SetUserVisibleHintViolation .

Это нарушение указывает на вызов метода setUserVisibleHint() , который устарел.

Если вы вызываете этот метод вручную, вместо этого вызовите setMaxLifecycle() . Если вы переопределите этот метод, переместите поведение в onResume() при передаче true и onPause() при передаче false .

Использование целевого фрагмента

Нарушение использования целевого фрагмента включается с помощью detectTargetFragmentUsage() и выдает TargetFragmentUsageViolation .

Это нарушение указывает на вызов методов setTargetFragment() , getTargetFragment() или getTargetRequestCode() , которые устарели. Вместо использования этих методов зарегистрируйте FragmentResultListener . Дополнительные сведения о передаче результатов см. в разделе Передача результатов между фрагментами .

Неправильный контейнер фрагментов

Нарушение контейнера неправильного фрагмента включается с помощью detectWrongFragmentContainer() и выдается WrongFragmentContainerViolation .

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