Ce guide présente les outils que vous pouvez utiliser pour déboguer vos fragments.
Journalisation FragmentManager
FragmentManager
est capable d'émettre divers messages vers Logcat. Cette fonctionnalité est désactivée par défaut pour éviter d'ajouter du bruit à Logcat, mais ces messages de journal peuvent parfois vous aider à résoudre les problèmes liés à vos fragments. FragmentManager
émet le résultat le plus pertinent au niveau des journaux Debug (débogage) et Verbose (verbosité).
Vous pouvez activer la journalisation à l'aide de la commande adb shell
:
adb shell setprop log.tag.FragmentManager DEBUG
Vous pouvez également activer la journalisation détaillée :
adb shell setprop log.tag.FragmentManager VERBOSE
Si vous activez la journalisation détaillée, vous pouvez appliquer un filtre au niveau du journal dans la fenêtre Logcat. Notez toutefois que dans ce cas, tous les journaux seront filtrés, pas uniquement les journaux FragmentManager
. Il est généralement préférable d'activer la journalisation FragmentManager
uniquement au niveau de journalisation dont vous avez besoin.
Journalisation de débogage
Au niveau DEBUG
, FragmentManager
émet généralement des messages de journal concernant les changements d'état du cycle de vie. Chaque entrée de journal contient le vidage toString()
de Fragment
.
Une entrée de journal contient les informations suivantes :
- Nom de classe simple de l'instance
Fragment
. - Code de hachage d'identité de l'instance
Fragment
. - Identifiant unique
FragmentManager
de l'instanceFragment
. Cette option est stable en cas de changements de configuration et de décès et recréations de processus. - Identifiant du conteneur auquel le
Fragment
a été ajouté, mais seulement s'il est défini. - Balise
Fragment
, mais seulement si elle est définie.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
Vous pouvez identifier le fragment à partir des éléments suivants :
- La classe
Fragment
estNavHostFragment.
- Le code de hachage de l'identité est
92d8f1d
. - L'identifiant unique est
fd92599e-c349-4660-b2d6-0ece9ec72f7b
. - L'identifiant du conteneur est
0x7f080116
. - La balise est omise, car aucune balise n'a été définie. Si une balise est présente, elle suit l'identifiant et utilise le format
tag=tag_value
.
Par souci de concision et de lisibilité, les UUID ont été raccourcis dans les exemples suivants.
Ici, un NavHostFragment
est initialisé, puis le startDestination
Fragment
de type FirstFragment
est créé et passe à l'état 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)
Suite à une interaction avec l'utilisateur, FirstFragment
quitte les différents états du cycle de vie. Ensuite, SecondFragment
est instancié et passe à l'état 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)
Un identifiant est attribué à toutes les instances de fragment. Cela vous permet de suivre différentes instances de la même classe Fragment
.
Journalisation détaillée
Au niveau VERBOSE
, FragmentManager
émet généralement des messages de journal concernant son état interne :
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)
Cela ne concerne que le chargement sur FirstFragment
. L'inclusion de la transition vers SecondFragment
aurait considérablement augmenté les entrées de journal.
Une grande partie des messages de journal de niveau VERBOSE
ne seront que peu, voire pas du tout utiles aux développeurs d'applications. Toutefois, il peut être utile de détecter les modifications apportées à la pile "Retour" pour résoudre certains problèmes.
StrictMode pour les fragments
Les versions 1.4.0 et ultérieures de la bibliothèque Fragment de Jetpack incluent le StrictMode pour les fragments. Elles peuvent détecter certains problèmes courants susceptibles d'entraîner un comportement inattendu de votre application.
Par défaut, Fragment StrictMode dispose d'une règle LAX
qui ne détecte rien. Il est toutefois possible de créer des règles personnalisées.
Une Policy
personnalisée définit les cas de non-respect détectés et précise les pénalités appliquées.
Pour appliquer une règle StrictMode personnalisée, attribuez-la au FragmentManager
.
Vous devez le faire dès que possible. Dans ce cas, effectuez cette opération dans un bloc init
ou dans le constructeur 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()); ... } }
Dans les cas où vous devez connaître Context
pour déterminer si vous souhaitez activer le mode strict (par exemple, à partir de la valeur d'une ressource booléenne), vous pouvez différer l'attribution d'une règle StrictMode à la classe FragmentManager
avec un 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()); ... } }
La dernière possibilité de configurer le mode strict afin de détecter toutes les violations possibles est dans onCreate()
. Toutefois, vous devez configurer le mode strict avant d'appeler 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()); ... } }
La règle utilisée dans ces exemples ne détecte que les cas de non-respect des règles concernant la réutilisation du fragment. Par ailleurs, l'application s'arrête chaque fois que cela se produit. penaltyDeath()
peut être utile pour les versions de débogage, car il échoue suffisamment rapidement pour que vous ne puissiez pas ignorer les violations.
Il est également possible d'autoriser certains cas de non-respect. Toutefois, dans l'exemple, cette règle applique ce non-respect pour tous les autres types de fragments. Cela est utile dans les cas où un composant de bibliothèque tiers peut contenir des cas de non-respect du StrictMode. Dans ce cas, vous pouvez ajouter temporairement ces cas de non-respect à la liste d'autorisation de votre StrictMode pour les composants dont vous n'êtes pas propriétaire jusqu'à ce que la bibliothèque corrige leur problème.
Consultez la documentation de FragmentStrictMode.Policy.Builder
pour savoir comment configurer d'autres cas de non-respect.
Il existe trois types de pénalités.
penaltyLog()
transmet les détails des cas de non-respect auLogCat
.penaltyDeath()
met fin à l'application lorsque des infractions sont détectées.penaltyListener()
vous permet d'ajouter un écouteur personnalisé, qui est appelé chaque fois que des infractions sont détectées.
Vous pouvez appliquer n'importe quelle combinaison de sanctions dans votre Policy
. Si votre règle ne spécifie pas explicitement une pénalité, la valeur par défaut penaltyLog()
est appliquée. Si vous appliquez une pénalité autre que penaltyLog()
dans votre Policy
personnalisé, penaltyLog()
sera désactivé, sauf si vous le définissez explicitement.
penaltyListener()
peut être utile lorsque vous souhaitez consigner les cas de non-respect dans une bibliothèque de journalisation tierce. Vous pouvez également activer la détection des cas d'erreurs non fatales dans les builds et les enregistrer dans une bibliothèque de rapports d'erreur. Cette stratégie peut détecter les cas de non-respect des règles.
Il est préférable de définir une règle générale StrictMode en définissant une règle par défaut qui s'applique à toutes les instances FragmentManager
via la méthode 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()); } }
Les sections suivantes décrivent les types d'infractions et les solutions de contournement possibles.
Réutilisation de fragments
La violation de réutilisation de fragment est activée à l'aide de detectFragmentReuse()
et génère une erreur FragmentReuseViolation
.
Cette infraction indique la réutilisation d'une instance Fragment
après sa suppression de FragmentManager
. Cette réutilisation peut entraîner des problèmes, car Fragment
peut conserver l'état de son utilisation précédente et ne pas se comporter de manière cohérente. Si vous créez une instance à chaque fois, elle est toujours dans son état initial lorsqu'elle est ajoutée à FragmentManager
.
Utilisation des balises de fragment
Le cas de non-respect d'utilisation de la balise de fragment est activé à l'aide de detectFragmentTagUsage()
et génère une erreur FragmentTagUsageViolation
.
Cette infraction indique qu'une valeur Fragment
a été gonflée à l'aide de la balise <fragment>
dans une mise en page XML. Pour résoudre ce problème, gonflez votre Fragment
dans <androidx.fragment.app.FragmentContainerView>
plutôt que dans la balise <fragment>
. Les fragments gonflés à l'aide d'un FragmentContainerView
peuvent gérer de manière fiable les transactions Fragment
et les modifications de configuration. Ces opérations risquent de ne pas fonctionner comme prévu si vous utilisez la balise <fragment>
.
Conserver l'utilisation des instances
Le cas de non-respect des règles de conservation des instances est activé à l'aide de la méthode detectRetainInstanceUsage()
et génère une erreur RetainInstanceUsageViolation
.
Cette infraction indique l'utilisation d'un Fragment
conservé. Plus précisément, des appels existants à setRetainInstance()
ou à getRetainInstance()
qui sont tous deux obsolètes.
Au lieu d'utiliser ces méthodes pour gérer vous-même les instances Fragment
conservées, vous devez stocker l'état dans un ViewModel
qui s'en chargera pour vous.
Définir l'indicateur visible par l'utilisateur
Le cas de non-respect de définition de l'indicateur visible par l'utilisateur est activé à l'aide de detectSetUserVisibleHint()
et génère une erreur SetUserVisibleHintViolation
.
Cette infraction indique un appel à setUserVisibleHint()
, qui est obsolète.
Si vous appelez cette méthode manuellement, vous devez appeler setMaxLifecycle()
. Si vous remplacez cette méthode, vous devez déplacer le comportement vers onResume()
lors de la transmission de true
et onPause()
lors de la transmission de false
.
Utilisation du fragment cible
Le cas de non-respect d'utilisation du fragment cible est activée à l'aide de detectTargetFragmentUsage()
et génère une erreur TargetFragmentUsageViolation
.
Cette infraction indique un appel à setTargetFragment()
, getTargetFragment()
ou getTargetRequestCode()
, qui sont tous obsolètes. Au lieu d'utiliser ces méthodes, vous devez enregistrer un FragmentResultListener
. Pour en savoir plus, consultez la section Transmettre des résultats entre plusieurs fragments.
Conteneur de fragment incorrect
Le cas de non-respect du conteneur de fragment incorrect est activé à l'aide de detectWrongFragmentContainer()
et génère une erreur WrongFragmentContainerViolation
.
Cette infraction indique l'ajout d'un Fragment
à un conteneur autre que FragmentContainerView
. Comme pour l'utilisation des balises de fragment, les transactions de fragment peuvent ne pas fonctionner comme prévu, sauf si elles sont hébergées dans une propriété FragmentContainerView
. Elle permet également de résoudre un problème dans l'API View
, qui entraîne le tracé de fragments utilisant des animations de sortie au-dessus de tous les autres fragments.