Panduan ini membahas alat yang dapat Anda gunakan untuk men-debug fragmen.
Logging FragmentManager
FragmentManager
dapat memunculkan berbagai pesan ke
Logcat. Fitur ini dinonaktifkan secara default,
tetapi terkadang pesan log ini dapat membantu Anda memecahkan
masalah pada fragmen. FragmentManager
memberikan output yang paling berharga
pada level log DEBUG
dan VERBOSE
.
Anda dapat mengaktifkan logging menggunakan perintah
adb shell
:
adb shell setprop log.tag.FragmentManager DEBUG
Atau, Anda dapat mengaktifkan logging panjang sebagai berikut:
adb shell setprop log.tag.FragmentManager VERBOSE
Dengan mengaktifkan logging panjang, Anda dapat menerapkan
filter level log di jendela Logcat. Namun, tindakan ini
akan memfilter semua log, bukan hanya log FragmentManager
. Sebaiknya Anda
mengaktifkan logging FragmentManager
hanya pada level log yang dibutuhkan.
Logging DEBUG
Pada level DEBUG
, FragmentManager
umumnya memunculkan pesan log yang berkaitan dengan
perubahan status siklus proses. Setiap entri log berisi dump toString()
dari Fragment
.
Entri log terdiri dari informasi berikut:
- Nama class sederhana dari instance
Fragment
. - Kode hash identitas
instance
Fragment
. - ID unik pengelola fragmen dari instance
Fragment
. ID ini akan stabil meskipun terjadi perubahan konfigurasi serta penghentian dan pembuatan ulang proses. - ID penampung tempat
Fragment
ditambahkan, tetapi hanya jika ditetapkan. - Tag
Fragment
, tetapi hanya jika ditetapkan.
Berikut adalah contoh entri log DEBUG
:
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
- Class
Fragment
adalahNavHostFragment
. - Kode hash identitas adalah
92d8f1d
. - ID unik adalah
fd92599e-c349-4660-b2d6-0ece9ec72f7b
. - ID penampung adalah
0x7f080116
. - Tag dihilangkan karena tidak ada yang ditetapkan. Jika ada, elemen ini akan mengikuti
ID dalam format
tag=tag_value
.
Agar lebih singkat dan mudah dibaca, UUID dalam contoh berikut disingkat.
Berikut adalah NavHostFragment
yang diinisialisasi, lalu startDestination
Fragment
dari jenis FirstFragment
yang dibuat dan bertransisi ke
status 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)
Setelah interaksi pengguna, FirstFragment
bertransisi keluar dari
berbagai status siklus proses. Kemudian, instance SecondFragment
akan dibuat dan bertransisi
ke status 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)
Semua instance Fragment
diberi akhiran oleh ID sehingga Anda dapat melacak
berbagai instance dari
class Fragment
yang sama.
Logging PANJANG
Pada tingkat VERBOSE
, FragmentManager
umumnya memunculkan pesan log tentang
status internalnya:
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)
Contoh ini hanya mencakup pemuatan di FirstFragment
. Menyertakan transisi ke
SecondFragment
akan meningkatkan entri log secara signifikan.
Banyak pesan log level VERBOSE
tidak terlalu berguna bagi developer
aplikasi. Namun, melihat waktu terjadinya perubahan pada data sebelumnya dapat membantu proses
debug beberapa masalah.
StrictMode untuk fragmen
Library Jetpack Fragment
versi 1.4.0 dan yang lebih baru menyertakan
StrictMode untuk fragmen. Mode ini dapat mendeteksi beberapa masalah umum yang dapat menyebabkan
aplikasi berperilaku tidak terduga. Untuk mengetahui informasi selengkapnya tentang menggunakan
StrictMode
, lihat StrictMode.
Policy
kustom
menentukan pelanggaran yang terdeteksi dan menentukan hukuman yang diterapkan
untuk pelanggaran yang terdeteksi.
Untuk menerapkan kebijakan StrictMode kustom, tetapkan kebijakan tersebut ke
FragmentManager
.
Lakukan hal ini sedini mungkin. Dalam hal ini, Anda melakukannya dalam
blok init
atau di konstruktor 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()); ... } }
Untuk kasus yang mengharuskan Anda mengetahui Context
guna menentukan apakah akan
mengaktifkan StrictMode, seperti dari nilai resource boolean, Anda dapat
menunda penetapan kebijakan StrictMode ke FragmentManager
menggunakan
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()); ... } }
Titik terbaru tempat Anda dapat mengonfigurasi StrictMode untuk menangkap semua
kemungkinan pelanggaran adalah dalam
onCreate()
,
sebelum panggilan ke 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()); ... } }
Kebijakan yang digunakan dalam contoh ini hanya mendeteksi pelanggaran adanya penggunaan ulang fragmen,
dan aplikasi akan berakhir setiap kali pelanggaran terjadi. penaltyDeath()
dapat
bermanfaat untuk mem-build debug karena cenderung mudah gagal sehingga Anda tidak dapat mengabaikan
pelanggaran.
Anda juga dapat memilih untuk mengizinkan pelanggaran tertentu. Namun, kebijakan yang digunakan dalam contoh sebelumnya akan menerapkan pelanggaran ini untuk semua jenis fragmen lainnya. Tindakan ini berguna untuk kasus-kasus saat komponen library pihak ketiga mungkin berisi pelanggaran StrictMode.
Dalam kasus semacam itu, Anda dapat menambahkan pelanggaran tersebut sementara waktu ke daftar yang disetujui StrictMode untuk komponen yang tidak dimiliki hingga library memperbaiki pelanggarannya.
Untuk mengetahui detail tentang cara mengonfigurasi pelanggaran lainnya, lihat dokumentasi untuk
FragmentStrictMode.Policy.Builder
.
Ada tiga jenis penalti.
penaltyLog()
menghapus detail pelanggaran ke Logcat.penaltyDeath()
menghentikan aplikasi saat pelanggaran terdeteksi.penaltyListener()
memungkinkan Anda menambahkan pemroses kustom yang dipanggil setiap kali pelanggaran terdeteksi.
Anda dapat menerapkan kombinasi penalti apa pun di Policy
. Jika kebijakan Anda tidak secara eksplisit menentukan penalti, penaltyLog()
default akan diterapkan. Jika Anda
menerapkan sanksi selain penaltyLog()
di Policy
kustom,
penaltyLog()
akan dinonaktifkan, kecuali jika Anda menyetelnya secara eksplisit.
penaltyListener()
dapat berguna jika Anda memiliki library logging pihak ketiga yang ingin
Anda catat pelanggarannya ke dalam log. Selain itu, Anda mungkin ingin mengaktifkan
pelanggaran non-fatal yang terjadi pada build rilis dan mencatatnya ke dalam log
library error. Strategi ini dapat mendeteksi pelanggaran yang terlewatkan.
Untuk menetapkan kebijakan StrictMode global, tetapkan kebijakan default yang berlaku untuk semua
instance FragmentManager
menggunakan
metode
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()); } }
Bagian berikut ini menjelaskan jenis pelanggaran dan solusinya yang dapat diterapkan.
Penggunaan ulang fragmen
Pelanggaran penggunaan ulang fragmen diaktifkan menggunakan
detectFragmentReuse()
dan menampilkan
FragmentReuseViolation
.
Pelanggaran ini menunjukkan penggunaan ulang instance Fragment
setelah dihapus
dari FragmentManager
. Penggunaan ulang ini dapat menyebabkan masalah karena Fragment
mungkin
akan mempertahankan status dari penggunaan sebelumnya dan tidak berperilaku konsisten. Setiap kali Anda
membuat instance baru, instance tersebut akan selalu berada dalam status awal ketika ditambahkan ke
FragmentManager
.
Penggunaan tag fragmen
Pelanggaran penggunaan tag fragmen diaktifkan menggunakan
detectFragmentTagUsage()
dan menampilkan
FragmentTagUsageViolation
.
Pelanggaran ini menunjukkan bahwa Fragment
di-inflate menggunakan tag <fragment>
dalam tata letak XML. Untuk mengatasi hal ini, inflate Fragment
Anda di dalam
<androidx.fragment.app.FragmentContainerView>
, buka dalam tag
<fragment>
. Fragmen yang di-inflate menggunakan FragmentContainerView
dapat menangani transaksi
Fragment
dan perubahan konfigurasi tanpa masalah. Fragmen ini mungkin tidak berfungsi
seperti yang diharapkan jika Anda menggunakan tag <fragment>
.
Mempertahankan penggunaan instance
Pelanggaran mempertahankan penggunaan instance diaktifkan menggunakan
detectRetainInstanceUsage()
dan menampilkan
RetainInstanceUsageViolation
.
Pelanggaran ini menunjukkan penggunaan Fragment
yang dipertahankan, khususnya, jika
ada panggilan ke
setRetainInstance()
atau
getRetainInstance()
,
yang keduanya tidak digunakan lagi.
Daripada menggunakan metode ini untuk mengelola sendiri instance Fragment
yang dipertahankan, simpan status di
ViewModel
yang akan menanganinya untuk Anda.
Menyetel petunjuk yang dapat dilihat pengguna
Pelanggaran petunjuk yang terlihat oleh pengguna yang ditetapkan diaktifkan menggunakan
detectSetUserVisibleHint()
dan menampilkan
SetUserVisibleHintViolation
.
Pelanggaran ini menunjukkan panggilan ke
setUserVisibleHint()
,
yang sudah tidak digunakan lagi.
Jika Anda memanggil metode ini secara manual, panggil
setMaxLifecycle()
. Jika Anda mengganti metode ini, pindahkan perilakunya ke
onResume()
saat meneruskan true
dan
onPause()
saat meneruskan false
.
Menargetkan penggunaan fragmen
Pelanggaran penggunaan fragmen target diaktifkan menggunakan
detectTargetFragmentUsage()
dan menampilkan
TargetFragmentUsageViolation
.
Pelanggaran ini menunjukkan panggilan ke
setTargetFragment()
,
getTargetFragment()
,
atau getTargetRequestCode()
,
yang semuanya sudah tidak digunakan lagi. Namun, daftarkan
FragmentResultListener
, bukan menggunakan metode ini. Untuk mengetahui informasi selengkapnya tentang meneruskan hasil, lihat Meneruskan hasil di antara
fragmen.
Penampung fragmen yang salah
Pelanggaran penampung fragmen yang salah diaktifkan menggunakan
detectWrongFragmentContainer()
dan menampilkan
WrongFragmentContainerViolation
.
Pelanggaran ini menunjukkan penambahan Fragment
ke penampung selain
FragmentContainerView
. Seperti penggunaan tag Fragment
,
transaksi fragmen mungkin tidak berfungsi seperti yang diharapkan, kecuali jika dihosting di dalam
FragmentContainerView
. Penggunaan tampilan penampung juga membantu mengatasi masalah pada View
API yang
menyebabkan fragmen yang menggunakan animasi keluar akan digambar di atas semua
fragmen lainnya.