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
toNavHostFragment
. - 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.