Este guia aborda ferramentas que podem ser usadas para depurar fragmentos.
Gerar registros do FragmentManager
O FragmentManager
é capaz de emitir várias mensagens para o
Logcat. Esse recurso é desativado por padrão para evitar a adição
de ruídos ao Logcat, mas algumas vezes essas mensagens de registro podem ajudar você a resolver
problemas com os fragmentos. FragmentManager
emite a saída mais significativa
nos níveis de registro Debug (Depurado) e Verbose (Detalhado).
É possível ativar a geração de registros usando o
comando adb shell
:
adb shell setprop log.tag.FragmentManager DEBUG
Como alternativa, ative o registro detalhado:
adb shell setprop log.tag.FragmentManager VERBOSE
Se você ativar o registro detalhado, vai poder aplicar um filtro de nível
de registro na janela do Logcat. No entanto, ele
filtra todos os registros, não apenas os registros do FragmentManager
. Geralmente, é melhor
ativar a geração de registros do FragmentManager
apenas no nível de registro necessário.
Gerar registros DEBUG
No nível DEBUG
, o FragmentManager
geralmente emite mensagens de registro relacionadas a
mudanças no estado do ciclo de vida. Cada entrada de registro contém o despejo
toString()
do Fragment
.
Uma entrada de registro consiste nas seguintes informações:
- O nome de classe simples da instância do
Fragment
. - O código hash de identidade
da instância do
Fragment
. - O ID exclusivo do
FragmentManager
da instância doFragment
. Ele é estável entre as mudanças de configuração e a interrupção e recriação de processos. - O ID do contêiner em que o
Fragment
foi adicionado, mas apenas se definido. - A tag do
Fragment
, mas apenas se definida.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
É possível identificar o fragmento usando as seguintes informações:
- A classe do
Fragment
éNavHostFragment.
. - O código hash de identidade é
92d8f1d
. - O ID exclusivo é
fd92599e-c349-4660-b2d6-0ece9ec72f7b
. - O ID do contêiner é
0x7f080116
. - A tag foi omitida porque nenhuma foi definida. Se ela estiver presente, o ID
vai seguir o formato
tag=tag_value
.
Para simplificar e facilitar a leitura, os UUIDs foram encurtados nos exemplos a seguir.
Aqui, vemos um NavHostFragment
inicializado e o startDestination
do Fragment
do tipo FirstFragment
sendo criado e passando para o
estado 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)
Após uma interação do usuário, vemos a transição FirstFragment
fora dos
vários estados do ciclo de vida. Em seguida, o SecondFragment
é instanciado e faz a transição
para o estado 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)
Todas as instâncias de fragmento são sufixadas por um identificador para que você possa rastrear
instâncias diferentes da
mesma classe Fragment
.
Gerar registros VERBOSE
No nível VERBOSE
, o FragmentManager
geralmente emite mensagens de registro sobre o próprio
estado interno:
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)
Isso abrange apenas o carregamento no FirstFragment
. Incluir a transição para o
SecondFragment
aumentaria consideravelmente as entradas de registro.
Grande parte das mensagens de registro do nível VERBOSE
é de pouca ou nenhuma utilidade para desenvolvedores
de apps. No entanto, ver quando ocorrem as mudanças na backstack pode ajudar a
depurar alguns problemas.
StrictMode para fragmentos
A versão 1.4.0 e versões mais recentes da biblioteca Jetpack Fragment incluem StrictMode para fragmentos. Ele pode detectar alguns problemas comuns que podem fazer com que o app se comporte de maneiras inesperadas.
Por padrão, o StrictMode para fragmentos tem uma
política
LAX
que não detecta nada. No entanto, é possível criar políticas personalizadas.
Uma
Policy
personalizada define quais violações são detectadas e especifica qual penalidade é aplicada
quando violações são detectadas.
Para aplicar uma política StrictMode personalizada, atribua-a ao
FragmentManager
.
Faça isso o quanto antes. Nesse caso, isso é feito em um
bloco init
ou no construtor 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()); ... } }
Nos casos em que você precisa saber o Context
para determinar se o modo restrito deve ou não ser ativado,
por exemplo, no valor de um recurso booleano, é possível
adiar a atribuição de uma política StrictMode para o FragmentManager
usando um
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()); ... } }
O último ponto em que é necessário configurar o modo restrito para capturar todas as violações
possíveis é em
onCreate()
,
mas é necessário configurar esse modo antes da chamada para 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()); ... } }
A política usada nesses exemplos detecta apenas violações de reutilização de fragmento,
e o app é encerrado sempre que elas ocorrem. penaltyDeath()
pode ser
útil em builds de depuração porque ele falha rápido o suficiente para que você não possa ignorar
violações.
Também é possível autorizar seletivamente algumas violações. Contudo, no exemplo, a política aplica essa violação para todos os outros tipos de fragmento. Isso é útil nos casos em que um componente da biblioteca de terceiros pode conter violações do StrictMode. Nesses casos, você pode adicionar temporariamente essas violações à lista de permissões do StrictMode para componentes que não pertencem a você até que a biblioteca corrija a violação.
Consulte a documentação do
FragmentStrictMode.Policy.Builder
para ver detalhes sobre como configurar outras violações.
Há três tipos de penalidade.
penaltyLog()
despeja detalhes de violações para oLogCat
.penaltyDeath()
encerra o app quando as violações são detectadas.penaltyListener()
permite adicionar um listener personalizado, que é chamado sempre que violações são detectadas.
Você pode aplicar qualquer combinação de penalidades na sua Policy
. Se a política não
especificar explicitamente uma penalidade, um padrão penaltyLog()
será aplicado. Se você
aplicar uma penalidade diferente de penaltyLog()
na sua Policy
personalizada,
o penaltyLog()
vai ser desativado, a menos que você o defina explicitamente.
penaltyListener()
pode ser útil quando você tem uma biblioteca de registros de terceiros na
qual quer registrar violações. Como alternativa, convém ativar
a detecção de violações não fatais em builds de lançamento e registrá-las em uma biblioteca de relatórios
de erros. É possível detectar violações em geral com essa estratégia.
A melhor maneira de definir uma política global StrictMode é configurar uma política padrão que
se aplique a
todas as instâncias
FragmentManager
pelo método
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()); } }
As seções a seguir descrevem os tipos de violação e possíveis soluções.
Reutilizar fragmentos
A violação de reutilização de fragmento é ativada usando
detectFragmentReuse()
e gera uma
FragmentReuseViolation
.
Essa violação indica a reutilização de uma instância de Fragment
após a remoção dele
do FragmentManager
. Essa reutilização pode causar problemas porque o Fragment
pode
manter o estado do uso anterior e não se comportar de forma consistente. Se você criar uma
nova instância todas as vezes, ela estará sempre no estado inicial quando adicionada ao
FragmentManager
.
Uso de tags de fragmento
A violação de uso da tag de fragmento é ativada usando
detectFragmentTagUsage()
e gera uma
FragmentTagUsageViolation
.
Essa violação indica que um Fragment
foi inflado usando a tag <fragment>
em um layout XML. Para resolver isso, infle seu Fragment
dentro de
<androidx.fragment.app.FragmentContainerView>
em vez de usar a tag
<fragment>
. Fragmentos inflados com uma FragmentContainerView
lidam com
transações de Fragment
e mudanças de configuração de maneira confiável. Eles podem não funcionar como
esperado se você usar a tag <fragment>
.
Uso da instância de retenção
A violação de uso da instância de retenção é ativada usando
detectRetainInstanceUsage()
e gera uma
RetainInstanceUsageViolation
.
Essa violação indica o uso de um Fragment
retido. Especificamente, se
há chamadas para
setRetainInstance()
ou
getRetainInstance()
que foram descontinuadas.
Em vez de usar esses métodos para gerenciar instâncias de Fragment
retidos, é preciso armazenar o estado em um
ViewModel
que vai cuidar isso para você.
Definir dica visível para o usuário
A violação de definição de uma dica visível ao usuário é ativada usando
detectSetUserVisibleHint()
e gera uma
SetUserVisibleHintViolation
.
Essa violação indica uma chamada para
setUserVisibleHint()
,
que foi descontinuada.
Se você estiver chamando esse método manualmente,
chame
setMaxLifecycle()
. Se você substituir esse método, mova o comportamento para
onResume()
ao transmitir true
e
onPause()
ao transmitir false
.
Uso do fragmento de destino
A violação de uso do fragmento de destino é ativada usando
detectTargetFragmentUsage()
e gera uma
TargetFragmentUsageViolation
.
Essa violação indica uma chamada para
setTargetFragment()
,
getTargetFragment()
ou getTargetRequestCode()
,
que foram descontinuadas. Em vez de usar esses métodos, registre um
FragmentResultListener
. Para ver mais informações, consulte Transmitir resultados entre
fragmentos.
Contêiner de fragmentos incorreto
A violação de contêiner de fragmento incorreto é ativada usando
detectWrongFragmentContainer()
e gera uma
WrongFragmentContainerViolation
.
Essa violação indica a adição de um Fragment
a um contêiner diferente de
FragmentContainerView
. Assim como no uso de tags de fragmento,
as transações de fragmentos podem não funcionar como esperado, a menos que hospedadas em uma
FragmentContainerView
. Isso também ajuda a resolver um problema na API View
que
faz com que fragmentos que usam animações de saída sejam desenhados sobre todos os
outros.