Fragmente debuggen

In dieser Anleitung werden Tools beschrieben, mit denen Sie Ihre Fragmente debuggen können.

FragmentManager-Logging

FragmentManager kann verschiedene Nachrichten an Logcat ausgeben. Dies ist standardmäßig deaktiviert. Manchmal können diese Logeinträge jedoch bei der Behebung von Problemen mit Fragmenten helfen. FragmentManager gibt die aussagekräftigste Ausgabe auf den Logebenen DEBUG und VERBOSE aus.

Sie können das Logging mit dem folgenden adb shell-Befehl aktivieren:

adb shell setprop log.tag.FragmentManager DEBUG

Alternativ können Sie die ausführliche Protokollierung so aktivieren:

adb shell setprop log.tag.FragmentManager VERBOSE

Wenn Sie das ausführliche Logging aktivieren, können Sie im Logcat-Fenster einen Filter auf Logebene anwenden. Dabei werden jedoch alle Logs gefiltert, nicht nur die FragmentManager-Logs. In der Regel ist es am besten, das FragmentManager-Logging nur auf der benötigten Logebene zu aktivieren.

Protokollierung der FEHLERBEHEBUNGEN

Auf DEBUG-Ebene gibt FragmentManager in der Regel Logeinträge aus, die sich auf Änderungen des Lebenszyklusstatus beziehen. Jeder Logeintrag enthält den toString()-Dump aus Fragment. Ein Logeintrag besteht aus den folgenden Informationen:

  • Der einfache Klassenname der Fragment-Instanz.
  • Der Identitäts-Hashcode der Fragment-Instanz.
  • Die eindeutige ID des Fragment-Managers der Instanz Fragment. Diese bleibt über Konfigurationsänderungen sowie über Vorgänge und Wiederherstellungen hinweg stabil.
  • Die ID des Containers, dem die Fragment hinzugefügt wird, jedoch nur, wenn sie festgelegt ist.
  • Das Fragment-Tag, falls festgelegt.

Hier ein Beispiel für einen DEBUG-Logeintrag:

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • Die Klasse Fragment ist NavHostFragment.
  • Der Identitäts-Hash-Code lautet 92d8f1d.
  • Die eindeutige ID lautet fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • Die Container-ID lautet 0x7f080116.
  • Das Tag wurde weggelassen, da keines festgelegt wurde. Wenn er vorhanden ist, folgt er der ID im Format tag=tag_value.

Der Einfachheit halber werden die UUIDs in den folgenden Beispielen gekürzt.

Hier ist eine NavHostFragment, die initialisiert wird, und dann das startDestination-Fragment vom Typ FirstFragment erstellt wird und in den RESUMED-Status übergeht:

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)

Nach einer Nutzerinteraktion wechselt FirstFragment aus den verschiedenen Lebenszyklusstatus. Anschließend wird SecondFragment instanziiert und in den Status RESUMED gewechselt:

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)

Alle Fragment-Instanzen haben eine Kennung als Suffix, sodass Sie verschiedene Instanzen derselben Fragment-Klasse verfolgen können.

VERBOSE-Protokollierung

Auf VERBOSE-Ebene gibt FragmentManager im Allgemeinen Lognachrichten über den internen Status aus:

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)

In diesem Beispiel geht es nur um das Laden in FirstFragment. Durch die Umstellung auf SecondFragment werden die Logeinträge erheblich erhöht. Viele der Lognachrichten der Ebene VERBOSE sind für App-Entwickler wenig nützlich. Bei der Fehlerbehebung einiger Probleme kann es jedoch hilfreich sein, zu sehen, wann Änderungen am Back-Stack auftreten.

StrictMode für Fragmente

Version 1.4.0 und höher der Jetpack Fragment-Bibliothek enthält StrictMode für Fragmente. Sie können einige häufige Probleme erkennen, die zu unerwartetem Verhalten Ihrer App führen können. Weitere Informationen zum Arbeiten mit StrictMode finden Sie unter StrictMode.

Ein benutzerdefinierter Policy definiert, welche Verstöße erkannt werden, und gibt an, welche Strafen bei erkannten Verstößen verhängt werden.

Wenn Sie eine benutzerdefinierte StrictMode-Richtlinie anwenden möchten, weisen Sie sie dem FragmentManager zu. Tun Sie dies so früh wie möglich. In diesem Fall erfolgt dies in einem init-Block oder im Java-Konstruktor:

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

In Fällen, in denen Sie die Context kennen müssen, um zu bestimmen, ob StrictMode aktiviert wird, z. B. aus dem Wert einer booleschen Ressource, können Sie die Zuweisung einer StrictMode-Richtlinie zu FragmentManager mit OnContextAvailableListener auf später verschieben:

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

Letzter Punkt, an dem Sie StrictMode so konfigurieren können, dass alle möglichen Verstöße abgefangen werden, befindet sich in onCreate() vor dem Aufruf von 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());
        ...
   }
}

Die in diesen Beispielen verwendete Richtlinie erkennt nur Verstöße gegen die Wiederverwendung von Fragmenten und die Anwendung wird beendet, sobald ein solcher Verstoß auftritt. penaltyDeath() kann in Builds zur Fehlerbehebung hilfreich sein, da es so schnell fehlschlägt, dass Verstöße nicht ignoriert werden können.

Es ist auch möglich, bestimmte Verstöße selektiv zuzulassen. Die im vorherigen Beispiel verwendete Richtlinie erzwingt diesen Verstoß jedoch für alle anderen Fragmenttypen. Dies ist nützlich, wenn eine Bibliothekskomponente eines Drittanbieters StrictMode-Verstöße enthalten kann.

In solchen Fällen können Sie die Verstöße vorübergehend der Zulassungsliste Ihres StrictModes für Komponenten hinzufügen, deren Inhaber Sie nicht sind, bis der Verstoß durch die Bibliothek behoben wurde.

Weitere Informationen zum Konfigurieren anderer Verstöße finden Sie in der Dokumentation zu FragmentStrictMode.Policy.Builder.

Es gibt drei Arten von Strafen.

  • penaltyLog() gibt Details zu Verstößen an Logcat aus.
  • penaltyDeath() beendet die App, wenn Verstöße erkannt werden.
  • Mit penaltyListener() können Sie einen benutzerdefinierten Listener hinzufügen, der immer dann aufgerufen wird, wenn Verstöße erkannt werden.

Du kannst in deinem Policy eine beliebige Kombination von Strafen anwenden. Wenn Ihre Richtlinie keine Strafe explizit angibt, wird der Standardwert penaltyLog() angewendet. Wenn Sie in Ihrer benutzerdefinierten Policy eine andere Strafe als penaltyLog() anwenden, wird penaltyLog() deaktiviert, sofern Sie dies nicht explizit festlegen.

penaltyListener() kann nützlich sein, wenn Sie die Logging-Bibliothek eines Drittanbieters haben, in der Sie Verstöße protokollieren möchten. Alternativ können Sie das Erkennen von nicht schwerwiegenden Verstößen in Release-Builds aktivieren und diese in einer Absturzberichtsbibliothek protokollieren. Mit dieser Strategie können Verstöße erkannt werden, die andernfalls übersehen wurden.

Zum Festlegen einer globalen StrictMode-Richtlinie legen Sie mit der Methode FragmentStrictMode.setDefaultPolicy() eine Standardrichtlinie fest, die für alle FragmentManager-Instanzen gilt:

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

In den folgenden Abschnitten werden die Arten von Verstößen und mögliche Problemumgehungen beschrieben.

Wiederverwendung von Fragmenten

Der Verstoß gegen die Wiederverwendung von Fragmenten wird mit detectFragmentReuse() aktiviert und gibt einen FragmentReuseViolation aus.

Dieser Verstoß weist auf die Wiederverwendung einer Fragment-Instanz nach ihrer Entfernung aus FragmentManager hin. Diese Wiederverwendung kann zu Problemen führen, da der Fragment möglicherweise den Status seiner vorherigen Verwendung beibehalten und sich nicht konsistent verhält. Wenn Sie jedes Mal eine neue Instanz erstellen, befindet sie sich immer im ursprünglichen Zustand, wenn sie zu FragmentManager hinzugefügt wird.

Fragment-Tag-Nutzung

Der Verstoß gegen die Fragment-Tag-Nutzung wird mit detectFragmentTagUsage() aktiviert und gibt einen FragmentTagUsageViolation aus.

Dieser Verstoß weist darauf hin, dass ein Fragment mit dem <fragment>-Tag in einem XML-Layout überhöht wurde. Sie können das Problem beheben, indem Sie Fragment in <androidx.fragment.app.FragmentContainerView> statt im <fragment>-Tag aufblähen. Fragmente, die mit einem FragmentContainerView aufgebläht wurden, können Fragment-Transaktionen und Konfigurationsänderungen zuverlässig verarbeiten. Diese funktionieren möglicherweise nicht wie erwartet, wenn Sie stattdessen das Tag <fragment> verwenden.

Instanznutzung beibehalten

Der Verstoß gegen die Nutzung der Aufbewahrungsinstanz wird mit detectRetainInstanceUsage() aktiviert und gibt einen RetainInstanceUsageViolation aus.

Dieser Verstoß weist auf die Verwendung eines beibehaltenen Fragment hin, insbesondere wenn es Aufrufe von setRetainInstance() oder getRetainInstance() gibt, die beide eingestellt wurden.

Anstatt diese Methoden zu verwenden, um beibehaltene Fragment-Instanzen selbst zu verwalten, speichern Sie den Status in einem ViewModel, das dies für Sie übernimmt.

Für den Nutzer sichtbaren Hinweis festlegen

Der festgelegte Verstoß gegen den für den Nutzer sichtbaren Hinweis wird mit detectSetUserVisibleHint() aktiviert und gibt einen SetUserVisibleHintViolation aus.

Dieser Verstoß weist auf einen Aufruf von setUserVisibleHint() hin, der eingestellt wurde.

Wenn Sie diese Methode manuell aufrufen, rufen Sie stattdessen setMaxLifecycle() auf. Wenn Sie diese Methode überschreiben, verschieben Sie das Verhalten zu onResume() bei der Übergabe von true und onPause() bei der Übergabe von false.

Nutzung des Zielfragments

Der Verstoß gegen die Nutzung des Zielfragments wird mit detectTargetFragmentUsage() aktiviert und gibt einen TargetFragmentUsageViolation aus.

Dieser Verstoß weist auf einen Aufruf von setTargetFragment(), getTargetFragment() oder getTargetRequestCode() hin, die alle eingestellt wurden. Registrieren Sie anstelle dieser Methoden ein FragmentResultListener. Weitere Informationen zum Übergeben von Ergebnissen finden Sie unter Ergebnisse zwischen Fragmenten übergeben.

Falscher Fragmentcontainer

Der falsche Fragmentcontainer-Verstoß wird mit detectWrongFragmentContainer() aktiviert und ein WrongFragmentContainerViolation ausgelöst.

Dieser Verstoß weist darauf hin, dass ein Fragment zu einem anderen Container als FragmentContainerView hinzugefügt wurde. Wie bei der Fragment-Tag-Nutzung funktionieren Fragmenttransaktionen möglicherweise nicht wie erwartet, es sei denn, sie werden in einem FragmentContainerView gehostet. Die Verwendung einer Containeransicht hilft auch beim Beheben eines Problems in der View API, das dazu führt, dass Fragmente, die Exit-Animationen verwenden, über alle anderen Fragmente gezeichnet werden.