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 untuk menghindari penambahan
derau ke Logcat, tetapi terkadang pesan log ini dapat membantu Anda memecahkan
masalah pada fragmen. FragmentManager
memberikan output yang paling berharga
pada level log Debug dan Panjang.
Anda dapat mengaktifkan logging menggunakan perintah
adb shell
:
adb shell setprop log.tag.FragmentManager DEBUG
Atau, Anda dapat mengaktifkan logging panjang:
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 tingkat 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
FragmentManager
dari instanceFragment
. 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.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
Anda dapat mengenali Fragment dari beberapa sifat berikut ini:
- 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 telah disingkat.
Di sini, kita melihat NavHostFragment
yang diinisialisasi dan startDestination
Fragment
dari jenis FirstFragment
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, kita melihat 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)
Pesan log ini hanya mencakup pemuatan di FirstFragment
. Menyertakan transisi ke
SecondFragment
akan meningkatkan entri log secara signifikan.
Sebagian besar pesan log level VERBOSE
akan hampir tidak atau tidak digunakan oleh 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.
Secara default, Fragment StrictMode memiliki
kebijakan
LAX
yang tidak dapat menangkap apa pun. Namun, Anda dapat membuat kebijakan khusus.
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
.
Anda harus melakukannya sesegera 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
untuk menentukan apakah akan
mengaktifkan mode ketat (misalnya, dari nilai resource boolean) atau tidak, 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 harus mengonfigurasi mode ketat untuk menangkap semua
kemungkinan pelanggaran adalah dalam
onCreate()
,
tetapi di sini Anda harus mengonfigurasi mode ketat 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. Pada contoh, kebijakan ini memberlakukan 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 ini sementara waktu ke daftar StrictMode untuk komponen yang tidak dimiliki hingga library memperbaiki pelanggarannya.
Baca dokumentasi
FragmentStrictMode.Policy.Builder
untuk detail cara mengonfigurasi pelanggaran lainnya.
Ada tiga jenis penalti.
penaltyLog()
menghapus detail pelanggaran keLogCat
penaltyDeath()
menghentikan aplikasi saat pelanggaran terdeteksi.penaltyListener()
memungkinkan Anda menambahkan pemroses kustom yang akan 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 dalam situasi apa pun.
Penetapan kebijakan StrictMode global sebaiknya dilakukan dengan
menetapkan kebijakan default yang
berlaku untuk semua
instance FragmentManager
melalui
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. Tag 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, Anda sebaiknya menyimpan status dalam
ViewModel
yang akan menangani pengelolaan ini 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 memanggil metode ini secara manual, maka Anda harus memanggil
setMaxLifecycle()
. Jika metode ini diganti, Anda harus memindahkan perilaku 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. Anda harus mendaftarkan FragmentResultListener
,
bukan menggunakan metode ini. Untuk informasi selengkapnya, 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 Fragmen,
Transaksi Fragment mungkin tidak berfungsi seperti yang diharapkan kecuali jika dihosting di dalam
FragmentContainerView
. Tindakan ini juga membantu mengatasi masalah pada View
API yang
menyebabkan fragmen yang menggunakan animasi keluar akan digambar di atas semua
fragmen lainnya.