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.