Debugowanie fragmentów

W tym przewodniku omawiamy narzędzia do debugowania fragmenty.

Logowanie FragmentManager

FragmentManager mogą wysyłać różne komunikaty Logcat. Ta opcja jest domyślnie wyłączona. ale czasami te komunikaty dziennika mogą pomóc w rozwiązaniu problemu. problemy z fragmentami. Funkcja FragmentManager generuje najbardziej istotne dane wyjściowe na poziomach logowania DEBUG i VERBOSE.

Logowanie możesz włączyć za pomocą tych opcji: Polecenie 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

Po włączeniu rejestrowania szczegółowego możesz zastosować poziom rejestrowania, w oknie Logcat. Jednak filtruje wszystkie logi, a nie tylko logi FragmentManager. Zwykle najlepiej jest włącz logowanie FragmentManager tylko na wymaganym poziomie logowania.

Logowanie DEBUG

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

  • Prosta nazwa klasy instancji Fragment.
  • Kod skrótu tożsamości instancji Fragment.
  • Unikalny identyfikator instancji Fragment w menedżerze fragmentów. Jest stabilna zmian konfiguracji oraz procesu śmierci i rekreacji.
  • Identyfikator kontenera, do którego dodano element Fragment, ale tylko wtedy, gdy jest on 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)
  • Zajęcia Fragment to NavHostFragment.
  • Kod skrótu tożsamości to 92d8f1d.
  • Unikalny identyfikator to fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • Identyfikator kontenera to 0x7f080116.
  • Tag został pominięty, ponieważ nie został ustawiony żaden tag. Jeśli jest obecny, wskazuje identyfikator w formacie tag=tag_value.

Aby zapewnić zwięzłość i czytelność, identyfikatory UUID są skracane w następujący sposób: przykłady.

Inicjuję: NavHostFragment, a potem startDestination Tworzę Fragment typu FirstFragment i przechodzi do: stan 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 w różnych stanach cyklu życia. Następnie powstaje instancja SecondFragment i przejścia 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ą prefiks identyfikatora, który umożliwia śledzenie różne wystąpienia tej samej klasy: Fragment.

WYBÓR SZCZEGÓŁOWE

Na poziomie VERBOSE FragmentManager zwykle wysyła komunikaty logu dotyczące swoich stan wewnętrzny:

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 obejmuje tylko wczytywanie na stronie FirstFragment. W tym przejście na SecondFragment znacznie zwiększa liczbę wpisów logu. Wiele komunikatów logu na poziomie VERBOSE jest rzadko używanych przez aplikacje dla programistów. Wiedza o tym, kiedy wystąpią zmiany w stosunku wstecznym, może pomóc i debugowanie niektórych problemów.

StrictMode dla fragmentów

Program jest w wersji 1.4.0 lub nowszej Biblioteka Jetpack Fragment zawiera StrictMode dla fragmentów. Wykryją typowe problemy, które powodują zachowanie aplikacji w nieoczekiwany sposób. Więcej informacji na temat współpracy z StrictMode, zobacz StrictMode.

Niestandardowy Policy określa, które naruszenia są wykrywane, i jaka kara zostanie zastosowana po wykryciu naruszeń.

Aby zastosować niestandardową zasadę StrictMode, przypisz ją do FragmentManager Zrób to jak najwcześniej. W tym przypadku trzeba 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ć Context, aby określić, czy włącz StrictMode, na przykład z wartości zasobu z wartością logiczną, możesz odrocz 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());
        ...
   }
}

Ostatni punkt, w którym możesz skonfigurować StrictMode tak, by wychwytywał wszystkie możliwe znajduje się w onCreate() przed połączeniem z numerem 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());
        ...
   }
}

Ta zasada używana w tych przykładach wykrywa tylko przypadki ponownego użycia fragmentów, a aplikacja przestaje działać, gdy tylko wystąpi jakiś błąd. penaltyDeath() może być jest przydatny w debugowaniu kompilacji, bo zawodzą one na tyle szybko, że nie można ich zignorować naruszenia zasad.

Możesz też zezwolić na wybiórcze wyświetlanie określonych naruszeń. Zasada stosowana w poprzedni przykład wymusza jednak to naruszenie we wszystkich pozostałych fragmentach . Jest to przydatne w przypadkach, gdy komponent biblioteki innej firmy może zawierają naruszenia StrictMode.

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

Szczegółowe informacje o konfigurowaniu innych naruszeń znajdziesz w dokumentacji FragmentStrictMode.Policy.Builder

Istnieją 3 typy kar.

  • penaltyLog() przesyła szczegóły naruszeń do narzędzia Logcat.
  • penaltyDeath() zamyka aplikację po wykryciu naruszeń.
  • penaltyListener() umożliwia dodanie niestandardowego detektora wywoływanego zawsze, gdy dojdzie do naruszenia – wykryto.

W Policy możesz zastosować dowolną kombinację kar. Jeśli Twoja zasada nie określono wyraźnie kary, zastosowana zostanie domyślna wartość penaltyLog(). Jeśli zastosuj karę inną niż penaltyLog() w niestandardowym elemencie Policy, a następnie Funkcja penaltyLog() jest wyłączona, chyba że została przez Ciebie ustawiona.

penaltyListener() może być przydatne, jeśli korzystasz z zewnętrznej biblioteki logów które mają być rejestrowane. Możesz też włączyć wykrywanie przypadków niekrytycznych w kompilacjach wersji i rejestrowanie ich w raportach o awariach. bibliotece. Ta strategia może wykrywać przypadki naruszenia zasad, które zostałyby pominięte.

Aby ustawić globalną zasadę StrictMode, ustaw domyślną zasadę, która będzie stosowana do wszystkich FragmentManager instancji za pomocą 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 tych problemów.

Ponowne wykorzystanie fragmentu

Naruszenie zasady ponownego użycia fragmentów jest włączone za pomocą: detectFragmentReuse() i rzuca FragmentReuseViolation

To naruszenie zasad wskazuje na ponowne użycie instancji Fragment po jej usunięciu od FragmentManager. To ponowne użycie może powodować problemy, ponieważ Fragment może zachowują stan od poprzedniego użycia i nie zachowują się spójnie. Jeśli utworzysz nowej instancji, po dodaniu do instancji jest ona zawsze w stanie początkowym FragmentManager

Użycie tagu fragmentu

Naruszenie zasad użycia tagu fragmentu jest włączone za pomocą tagu detectFragmentTagUsage() i rzuca FragmentTagUsageViolation

To naruszenie zasad wskazuje, że Fragment zawiera wartość zawyżoną przy użyciu <fragment> w układzie XML. Aby rozwiązać ten problem, zwiększ: Fragment <androidx.fragment.app.FragmentContainerView> zamiast w <fragment> . Fragmenty rozszerzone za pomocą FragmentContainerView są w stanie niezawodnie obsługiwać Liczba transakcji i zmian konfiguracji: Fragment. Mogą one nie działać jako oczekiwany w przypadku używania tagu <fragment>.

Zachowaj wykorzystanie instancji

Naruszenie zasad dotyczących zachowywania wykorzystania instancji jest włączone za pomocą detectRetainInstanceUsage() i rzuca RetainInstanceUsageViolation

To naruszenie zasad wskazuje na użycie zachowywanego elementu Fragment, zwłaszcza jeśli są połączenia do setRetainInstance(). lub getRetainInstance(), które zostały wycofane.

Zamiast używać tych metod do zarządzania zachowanymi instancjami Fragment zapisz stan w ViewModel. która zajmie się tym za Ciebie.

Ustaw wskazówkę widoczną dla użytkownika

Ustawianie naruszenia wskazówek dotyczących widocznych dla użytkownika jest włączone za pomocą: detectSetUserVisibleHint() i rzuca SetUserVisibleHintViolation

To naruszenie zasad wskazuje połączenie do setUserVisibleHint() który został wycofany.

Jeśli wywołujesz tę metodę ręcznie, wywołaj setMaxLifecycle() . Jeśli zastąpisz tę metodę, przenieś działanie do onResume() podczas przechodzenia w true i onPause() podczas przechodzenia w ciągu false.

Docelowe wykorzystanie fragmentu

Naruszenie zasad użycia docelowego fragmentu jest włączone za pomocą: detectTargetFragmentUsage() i rzuca TargetFragmentUsageViolation

To naruszenie zasad wskazuje połączenie do setTargetFragment() getTargetFragment(), lub getTargetRequestCode(), które zostały wycofane. Zamiast korzystać z tych metod, zarejestruj plik FragmentResultListener Więcej informacji o przekazywaniu wyników znajdziesz w artykule Przekazuj wyniki między Fragmenty kodu.

Nieprawidłowy kontener fragmentów

Włączono nieprawidłowe naruszenie kontenera z fragmentami za pomocą: detectWrongFragmentContainer() i rzuca WrongFragmentContainerViolation

To naruszenie zasad wskazuje, że zasób Fragment został dodany do kontenera innego niż FragmentContainerView Tak jak w przypadku korzystania z tagów Fragment, transakcje oparte na fragmentach mogą nie działać zgodnie z oczekiwaniami, chyba że będą hostowane w FragmentContainerView Użycie widoku kontenera pomaga też rozwiązać problem w interfejsie API View, który powoduje, że fragmenty korzystające z animacji wyjścia są rysowane nad wszystkimi pozostałymi fragmenty.