Debugowanie fragmentów

W tym przewodniku omawiamy narzędzia, których możesz użyć do debugowania swoich fragmentów.

Logowanie w FragmentManager

FragmentManager może wysyłać różne komunikaty do Logcat. Ta opcja jest domyślnie wyłączona, ale czasami komunikaty logu mogą pomóc w rozwiązywaniu problemów z fragmentami. FragmentManager przekazuje najbardziej przydatne dane wyjściowe na poziomach logów DEBUG i VERBOSE.

Logowanie możesz włączyć przy użyciu tego polecenia adb shell:

adb shell setprop log.tag.FragmentManager DEBUG

Możesz też włączyć logowanie szczegółowe w ten sposób:

adb shell setprop log.tag.FragmentManager VERBOSE

Jeśli włączysz logowanie szczegółowe, możesz zastosować filtr na poziomie logu w oknie Logcat. Spowoduje to jednak odfiltrowanie wszystkich logów, a nie tylko logów FragmentManager. Zwykle najlepiej jest włączyć logowanie FragmentManager tylko na odpowiednim poziomie logowania.

Logowanie DEBUG

Na poziomie DEBUG FragmentManager zwykle wysyła komunikaty logu dotyczące zmian stanu cyklu życia. Każdy wpis logu zawiera zrzut toString() z Fragment. Wpis w dzienniku zawiera następujące informacje:

  • Prosta nazwa klasy instancji Fragment.
  • Kod skrótu tożsamości instancji Fragment.
  • Unikalny identyfikator menedżera fragmentów Fragment. Jest to stabilne niezależnie od zmian konfiguracji oraz śmierci i odtwarzania procesów.
  • Identyfikator kontenera, do którego dodano element Fragment, ale tylko wtedy, gdy jest ustawiony.
  • Tag Fragment, ale tylko wtedy, gdy jest ustawiony.

Oto przykładowy wpis logu DEBUG:

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • Klasa Fragment to NavHostFragment.
  • Kod skrótu tożsamości to 92d8f1d.
  • Unikalny identyfikator to fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • Identyfikator kontenera: 0x7f080116.
  • Tag jest pomijany, ponieważ nie ustawiono żadnych ustawień. Jeśli występuje, ma identyfikator w formacie tag=tag_value.

Aby zachować zwięzłość i czytelność, w poniższych przykładach identyfikatory UUID są skrócone.

Oto inicjowany NavHostFragment, a następnie tworzony startDestination Fragment typu FirstFragment i przechodzący do stanu 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)

Po interakcji użytkownika FirstFragment przechodzi z różnych stanów cyklu życia. Następnie jest tworzone wystąpienie SecondFragment i przechodzi do stanu 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)

Wszystkie instancje Fragment mają sufiks, dzięki czemu można śledzić różne instancje tej samej klasy Fragment.

SZCZEGÓŁOWE zapisywanie

Na poziomie VERBOSE FragmentManager zwykle wysyła w dzienniku komunikaty o stanie wewnętrznym:

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)

Ten przykład dotyczy tylko wczytywania na FirstFragment. Uwzględnienie przejścia na SecondFragment znacznie zwiększa liczbę wpisów logu. Wiele komunikatów dziennika na poziomie VERBOSE nie przydaje się deweloperom. W debugowaniu niektórych problemów warto jednak sprawdzić, kiedy nastąpią zmiany w stosie wstecznym.

StrictMode dla fragmentów

Biblioteka Jetpack Fragment w wersji 1.4.0 lub nowszej zawiera StrictMode na potrzeby fragmentów. Wykrywa typowe problemy, przez które aplikacja może działać w nieoczekiwany sposób. Więcej informacji o pracy z zasadą StrictMode znajdziesz w sekcji StrictMode.

Niestandardowy Policy określa, które naruszenia są wykrywane i jakie kary są stosowane w przypadku ich wykrycia.

Aby zastosować niestandardową zasadę StrictMode, przypisz ją do FragmentManager. Zrób to jak najszybciej. W tym przypadku możesz to zrobić w bloku init lub w konstruktorze 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());
        ...
   }
}

W przypadkach, gdy musisz znać zasadę Context, aby określić, czy włączyć StrictMode, na przykład na podstawie wartości zasobu wartości logicznej, możesz opóźnić przypisanie zasady StrictMode do FragmentManager za pomocą 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());
        ...
   }
}

Najnowszy punkt, w którym możesz skonfigurować StrictMode w celu wykrywania wszystkich możliwych naruszeń, znajduje się w onCreate(), przed wywołaniem 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());
        ...
   }
}

Zasada użyta w tych przykładach wykrywa tylko naruszenia związane z ponownym używaniem fragmentów, a aplikacja jest zamykana w każdym przypadku. penaltyDeath() może być pomocny w debugowaniu kompilacji, ponieważ kończy się niepowodzeniem na tyle szybko, że nie można zignorować naruszeń.

Można też wybiórczo zezwalać na niektóre naruszenia. Zasada użyta w poprzednim przykładzie wymusza jednak to naruszenie w przypadku wszystkich innych typów fragmentów. Jest to przydatne w przypadkach, gdy komponent biblioteki innej firmy może zawierać naruszenia StrictMode.

W takich przypadkach możesz tymczasowo dodać te naruszenia do listy dozwolonych w StrictMode w przypadku komponentów, które nie należą do Ciebie, dopóki biblioteka nie usunie naruszeń.

Więcej informacji o konfigurowaniu innych naruszeń znajdziesz w dokumentacji FragmentStrictMode.Policy.Builder.

Są 3 rodzaje kar.

  • penaltyLog() zapisuje szczegółowe informacje o naruszeniach w Logcat.
  • penaltyDeath() zamyka aplikację po wykryciu naruszeń.
  • penaltyListener() umożliwia dodanie niestandardowego detektora, który jest wywoływany po każdym wykryciu naruszeń.

W Policy możesz zastosować dowolną kombinację kar. Jeśli Twoja zasada nie określa wyraźnie kary, stosowana jest domyślna wartość penaltyLog(). Jeśli zastosujesz w niestandardowym elemencie Policy karę inną niż penaltyLog(), wartość penaltyLog() będzie wyłączona, chyba że ją wyraźnie ustawisz.

Usługa penaltyListener() może być przydatna, jeśli masz zewnętrzną bibliotekę logowania, w której chcesz rejestrować naruszenia. Możesz też włączyć wychwytywanie kompilacji wersji o niekrytycznych naruszeniach i rejestrować je w bibliotece raportów o awariach. Ta strategia może wykrywać naruszenia, które w przeciwnym razie zostały pominięte.

Aby ustawić globalną zasadę StrictMode, ustaw zasadę domyślną, która będzie stosowana do wszystkich instancji FragmentManager korzystających z metody 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());
    }
}

W sekcjach poniżej opisujemy typy naruszeń i możliwe sposoby obejścia tego problemu.

Ponowne wykorzystanie fragmentu

Naruszenie ponownego użycia fragmentu jest włączone za pomocą metody detectFragmentReuse() i zwraca FragmentReuseViolation.

To naruszenie wskazuje na ponowne wykorzystanie instancji Fragment po jej usunięciu z FragmentManager. To ponowne użycie może powodować problemy, ponieważ Fragment może zachować stan z poprzedniego użycia i nie będzie działać spójnie. Jeżeli za każdym razem tworzysz nową instancję, po dodaniu do FragmentManager będzie ona zawsze w stanie początkowym.

Wykorzystanie tagu z fragmentem

Naruszenie związane z użyciem tagu fragmentu jest włączone za pomocą tagu detectFragmentTagUsage() i zwraca FragmentTagUsageViolation.

To naruszenie zasad wskazuje, że element Fragment został zawyżony za pomocą tagu <fragment> w układzie XML. Aby rozwiązać ten problem, zwiększ wartość Fragment w tagu <androidx.fragment.app.FragmentContainerView>, a nie w tagu <fragment>. Fragmenty powiększone za pomocą atrybutu FragmentContainerView mogą niezawodnie obsłużyć Fragment transakcji i zmian konfiguracji. Jeśli zamiast tego użyjesz tagu <fragment>, mogą one nie działać zgodnie z oczekiwaniami.

Zachowaj wykorzystanie instancji

Naruszenie dotyczące zachowywania wykorzystania instancji jest włączone za pomocą metody detectRetainInstanceUsage() i powoduje zgłoszenie RetainInstanceUsageViolation.

Naruszenie to wskazuje użycie zachowanej Fragment, zwłaszcza jeśli istnieją wywołania setRetainInstance() lub getRetainInstance(), które zostały wycofane.

Zamiast używać tych metod do samodzielnego zarządzania zachowanymi instancjami Fragment, przechowuj stan w ViewModel, który to robi za Ciebie.

Ustaw widoczną podpowiedź dla użytkownika

Naruszenie widocznych dla użytkownika wskazówek jest włączone za pomocą metody detectSetUserVisibleHint() i zwraca SetUserVisibleHintViolation.

To naruszenie wskazuje wywołanie metody setUserVisibleHint(), która została wycofana.

Jeśli wywołujesz tę metodę ręcznie, wywołaj zamiast tego metodę setMaxLifecycle(). Jeśli zastąpisz tę metodę, przenieś zachowanie do onResume() podczas przekazywania true i onPause() podczas przekazywania obiektu false.

Wykorzystanie fragmentu docelowego

Naruszenie wykorzystania fragmentu docelowego jest włączone za pomocą klasy detectTargetFragmentUsage() i zwraca TargetFragmentUsageViolation.

To naruszenie wskazuje na wywołanie setTargetFragment(), getTargetFragment() lub getTargetRequestCode(), które zostały wycofane. Zamiast używać tych metod, zarejestruj FragmentResultListener. Więcej informacji o przekazywaniu wyników znajdziesz w artykule Przekazywanie wyników między fragmentami.

Nieprawidłowy kontener fragmentu

Nieprawidłowe naruszenie kontenera fragmentów jest włączone w metodzie detectWrongFragmentContainer() i powoduje zgłoszenie WrongFragmentContainerViolation.

To naruszenie zasad wskazuje, że dodano element Fragment do kontenera innego niż FragmentContainerView. Tak jak w przypadku używania tagu Fragment, transakcje dotyczące fragmentów mogą nie działać zgodnie z oczekiwaniami, chyba że są hostowane w FragmentContainerView. Korzystanie z widoku kontenera pomaga też rozwiązać problem z interfejsem API View, który powoduje, że fragmenty z animacjami wyjścia są rysowane na wszystkich innych fragmentach.