Ce guide présente les outils que vous pouvez utiliser pour déboguer vos fragments.
Journalisation FragmentManager
FragmentManager
peut émettre divers messages vers Logcat. Cette fonctionnalité est désactivée par défaut, mais ces messages de journal peuvent vous aider à résoudre les problèmes liés à vos fragments. FragmentManager
émet le résultat le plus pertinent au niveau des journaux DEBUG
et VERBOSE
.
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 comme suit :
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. Cependant, tous les journaux sont 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
. - ID unique du gestionnaire de fragments de l'instance
Fragment
. 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.
Voici un exemple d'entrée de journal DEBUG
:
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
- 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 elle est présente, elle suit l'identifiant et utilise le format
tag=tag_value
.
Par souci de concision et de lisibilité, les UUID sont 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 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)
Ct exemple ne concerne que le chargement sur FirstFragment
. L'inclusion de la transition vers SecondFragment
augmente considérablement les entrées de journal.
Un grand nombre de messages de journal de niveau VERBOSE
sont peu utiles pour les 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. Pour en savoir plus sur l'utilisation de StrictMode
, consultez StrictMode.
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
.
Faites-le 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 StrictMode, 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()); ... } }
Le dernier point à partir duquel vous pouvez configurer le StrictMode pour détecter tous les cas de non-respect possibles est dans onCreate()
, avant l'appel à 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 cas de non-respect.
Il est également possible d'autoriser certains cas de non-respect. Toutefois, la règle utilisée dans l'exemple précédent applique ce cas de 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.
Pour découvrir comment configurer d'autres cas de non-respect, consultez la documentation de FragmentStrictMode.Policy.Builder
.
Il existe trois types de pénalités.
penaltyLog()
transmet les détails des cas de non-respect à Logcat.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.
Pour définir une règle StrictMode générale, définissez une règle par défaut qui s'appliquera à toutes les instances FragmentManager
à l'aide de 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é, en particulier en cas d'appels à 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, stockez l'état dans un ViewModel
qui gérera cela 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, appelez plutôt setMaxLifecycle()
. Si vous remplacez cette méthode, déplacez 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, enregistrez un FragmentResultListener
. Pour en savoir plus sur la transmission des résultats, 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 de la balise Fragment
, les transactions de fragment peuvent ne pas fonctionner comme prévu, sauf si elles sont hébergées dans une propriété FragmentContainerView
. L'utilisation d'une vue de conteneur 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.