Melakukan debug fragmen Anda

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 adalah NavHostFragment.
  • 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.