แก้ไขข้อบกพร่องของ Fragment

คู่มือนี้ครอบคลุมเครื่องมือที่คุณสามารถใช้เพื่อแก้ไขข้อบกพร่อง ส่วนย่อย

การบันทึก FragmentManager

FragmentManager สามารถส่งข้อความต่างๆ ไปยัง Logcat ตัวเลือกนี้จะปิดใช้โดยค่าเริ่มต้น แต่บางครั้งข้อความบันทึกเหล่านี้ก็ช่วยคุณแก้ปัญหาได้ กับส่วนย่อยของคุณ FragmentManager จะแสดงเอาต์พุตที่มีความหมายมากที่สุด ที่ระดับการบันทึก DEBUG และ VERBOSE

คุณเปิดใช้การบันทึกได้โดยใช้สิ่งต่อไปนี้ คำสั่ง adb shell

adb shell setprop log.tag.FragmentManager DEBUG

หรือคุณสามารถเปิดใช้การบันทึกแบบละเอียดได้ดังนี้

adb shell setprop log.tag.FragmentManager VERBOSE

หากเปิดใช้การบันทึกแบบละเอียด คุณจะใช้ระดับการบันทึกได้ ตัวกรองในหน้าต่าง Logcat อย่างไรก็ตาม กรองบันทึกทั้งหมด ไม่ใช่แค่บันทึก FragmentManager โดยปกติแล้วคุณควร เปิดใช้การบันทึก FragmentManager ในระดับการบันทึกที่คุณต้องการเท่านั้น

การบันทึกการแก้ไขข้อบกพร่อง

ที่ระดับ DEBUG โดยทั่วไปแล้ว FragmentManager จะส่งข้อความบันทึกที่เกี่ยวข้องกับ การเปลี่ยนแปลงสถานะของวงจรการใช้งาน รายการบันทึกแต่ละรายการจะมี toString() ดัมพ์จาก Fragment รายการบันทึกประกอบด้วยข้อมูลต่อไปนี้

  • ชื่อคลาสแบบง่ายของอินสแตนซ์ Fragment
  • รหัสแฮชข้อมูลประจำตัว ของอินสแตนซ์ Fragment
  • รหัสที่ไม่ซ้ำกันของตัวจัดการส่วนย่อยของอินสแตนซ์ Fragment คงที่ ในการเปลี่ยนแปลงการกำหนดค่า และประมวลผลการเสียชีวิตและการสร้างใหม่
  • รหัสของคอนเทนเนอร์ที่เพิ่ม Fragment แต่มีการตั้งค่าเท่านั้น
  • แท็ก Fragment แต่หากมีการตั้งค่าไว้เท่านั้น

ตัวอย่างรายการบันทึก DEBUG มีดังนี้

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • ชั้นเรียน Fragment คือ NavHostFragment
  • รหัสแฮชข้อมูลประจำตัวคือ 92d8f1d
  • รหัสที่ไม่ซ้ำกันคือ fd92599e-c349-4660-b2d6-0ece9ec72f7b
  • รหัสคอนเทนเนอร์คือ 0x7f080116
  • ระบบละเว้นแท็กเนื่องจากยังไม่ได้ตั้งค่า เมื่อปรากฏ ระบบจะดำเนินการตาม รหัสในรูปแบบ tag=tag_value

UUID จะถูกตัดให้สั้นลงเพื่อความกระชับและอ่านง่าย ดังนี้ ตัวอย่าง

นี่คือ NavHostFragment กำลังเริ่มต้น จากนั้นเป็น startDestination กำลังสร้าง Fragment ประเภท FirstFragment และเปลี่ยนไปเป็น สถานะ 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)

หลังจากการโต้ตอบของผู้ใช้ FirstFragment เปลี่ยนจาก มีสถานะต่างๆ ในวงจร จากนั้นระบบจะสร้างอินสแตนซ์ SecondFragment และการเปลี่ยน จนถึงสถานะ 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)

อินสแตนซ์ Fragment ทั้งหมดจะต่อท้ายด้วยตัวระบุเพื่อให้คุณติดตามได้ อินสแตนซ์ต่างๆ Fragment ชั้นเรียนเดียวกัน

การบันทึก VERBOSE

ที่ระดับ VERBOSE โดยปกติ FragmentManager จะแสดงข้อความบันทึกเกี่ยวกับ สถานะภายใน:

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)

ตัวอย่างนี้ครอบคลุมเฉพาะการโหลดใน FirstFragment เท่านั้น รวมถึงการเปลี่ยนไปใช้ SecondFragment จะเพิ่มรายการบันทึกอย่างมาก ข้อความในบันทึกระดับ VERBOSE จำนวนมากไม่ค่อยมีประโยชน์ต่อแอป อย่างไรก็ตาม การทราบว่าเมื่อใดที่การเปลี่ยนแปลงใดๆ ใน Back Stack เกิดขึ้นอาจช่วย การแก้ไขข้อบกพร่องบางอย่าง

StrictMode สำหรับส่วนย่อย

เวอร์ชัน 1.4.0 ขึ้นไป ไลบรารีของ Jetpack Fragment ประกอบด้วย StrictMode สำหรับส่วนย่อย เครื่องมือนี้สามารถตรวจพบปัญหาที่พบได้ทั่วไปบางประการซึ่งอาจทำให้ ให้ทำงานในลักษณะที่ไม่คาดคิด หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับการทำงานร่วมกับ StrictMode โปรดดู StrictMode

กำหนดเอง Policy ระบุการละเมิดที่ตรวจพบและระบุบทลงโทษ เมื่อตรวจพบการละเมิด

หากต้องการใช้นโยบาย StrictMode ที่กำหนดเอง ให้กำหนดนโยบายดังกล่าวให้กับ FragmentManager ดำเนินการนี้โดยเร็วที่สุด ในกรณีนี้ คุณจะดำเนินการใน init บล็อกหรือในเครื่องมือสร้าง 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());
        ...
   }
}

สำหรับกรณีที่จำเป็นต้องทราบ Context เพื่อกำหนดว่าต้อง เปิดใช้งาน StrictMode เช่น จากค่าทรัพยากรบูลีน คุณสามารถ เลื่อนการกำหนดนโยบาย StrictMode ให้กับ FragmentManager โดยใช้ 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());
        ...
   }
}

จุดล่าสุดที่คุณสามารถกำหนดค่า StrictMode ให้ตรวจจับที่เป็นไปได้ทั้งหมด การละเมิดจะอยู่ใน onCreate() ก่อนโทรหา 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());
        ...
   }
}

นโยบายนี้ที่ใช้ในตัวอย่างเหล่านี้จะตรวจจับเฉพาะการละเมิดการใช้ส่วนย่อยเท่านั้น และแอปจะสิ้นสุดลงเมื่อใดก็ตามเกิดขึ้น penaltyDeath() สามารถเป็นได้ มีประโยชน์ในการสร้างแก้ไขข้อบกพร่อง เพราะล้มเหลวอย่างรวดเร็วมากพอจนคุณมองข้าม การละเมิดดังกล่าว

นอกจากนี้ยังอาจเลือกอนุญาตการละเมิดบางอย่างได้ด้วย นโยบายที่ใช้ใน อย่างไรก็ตาม ตัวอย่างข้างต้นจะบังคับใช้การละเมิดนี้กับส่วนย่อยอื่นๆ ทั้งหมด ประเภทต่างๆ ซึ่งมีประโยชน์ในกรณีที่คอมโพเนนต์ไลบรารีของบุคคลที่สามอาจ มีการละเมิด StrictMode

ในกรณีเช่นนี้ คุณสามารถเพิ่มการละเมิดดังกล่าวลงในรายการที่อนุญาต StrictMode สำหรับคอมโพเนนต์ที่คุณไม่ได้เป็นเจ้าของจนกว่าจะถึงไลบรารี แก้ไขการละเมิด

สำหรับรายละเอียดเกี่ยวกับวิธีกำหนดค่าการละเมิดอื่นๆ โปรดดูเอกสารสำหรับ FragmentStrictMode.Policy.Builder

บทลงโทษมี 3 ประเภท

  • penaltyLog() ส่งออกรายละเอียดของการละเมิดไปยัง Logcat
  • penaltyDeath() จะหยุดแอปเมื่อตรวจพบการละเมิด
  • penaltyListener() ช่วยให้คุณเพิ่ม Listener แบบกำหนดเอง ซึ่งจะถูกเรียกทุกครั้งที่มีการละเมิด ตรวจพบ

คุณใช้การลงโทษแบบใดก็ได้ในPolicy หากนโยบายของคุณรองรับ ไม่ได้ระบุบทลงโทษอย่างชัดแจ้ง ระบบจะใช้ค่าเริ่มต้นเป็น penaltyLog() หากคุณ ใช้บทลงโทษอื่นที่ไม่ใช่ penaltyLog() ใน Policy ที่กำหนดเองของคุณ penaltyLog() จะถูกปิดใช้ เว้นแต่คุณจะตั้งค่านี้ไว้อย่างชัดแจ้ง

penaltyListener() จะมีประโยชน์เมื่อคุณมีไลบรารีการบันทึกของบุคคลที่สามเพื่อ ซึ่งคุณต้องการบันทึกการละเมิด หรือคุณอาจต้องการเปิดใช้ การละเมิดที่ไม่ร้ายแรงที่ตรวจพบในบิลด์ที่เผยแพร่และบันทึกการละเมิดไปยังรายงานข้อขัดข้อง ไลบรารี กลยุทธ์นี้สามารถตรวจจับการละเมิดที่อาจพลาดไป

หากต้องการตั้งค่านโยบาย StrictMode ส่วนกลาง ให้ตั้งค่านโยบายเริ่มต้นที่ใช้กับ FragmentManager อินสแตนซ์ที่ใช้ 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());
    }
}

ส่วนต่อไปนี้จะอธิบายประเภทของการละเมิดและวิธีแก้ไขเฉพาะหน้าที่เป็นไปได้

การใช้ Fragment ซ้ำ

คุณเปิดใช้การละเมิดการใช้ Fragment ซ้ำได้โดยใช้ detectFragmentReuse() และส่งข้อความ FragmentReuseViolation

การละเมิดนี้แสดงถึงการนำอินสแตนซ์ Fragment มาใช้ซ้ำหลังจากนำออก จาก FragmentManager การใช้ซ้ำนี้อาจทำให้เกิดปัญหาเนื่องจาก Fragment อาจ คงสถานะจากการใช้งานก่อนหน้าไว้และทำงานไม่สม่ำเสมอ หากคุณสร้าง อินสแตนซ์ใหม่ทุกครั้ง อินสแตนซ์จะอยู่ในสถานะเริ่มต้นเสมอเมื่อมีการเพิ่มลงใน FragmentManager

การใช้แท็ก Fragment

มีการเปิดใช้การละเมิดการใช้แท็กส่วนย่อยโดยใช้ detectFragmentTagUsage() และส่งข้อความ FragmentTagUsageViolation

การละเมิดนี้ชี้ว่า Fragment สูงเกินจริงโดยใช้ <fragment> ในการจัดวาง XML ในการแก้ไขปัญหานี้ ให้เพิ่ม Fragment ของคุณให้สูงเกินจริง <androidx.fragment.app.FragmentContainerView> แทนที่จะเป็นใน<fragment> แท็ก ส่วนย่อยที่พองขึ้นโดยใช้ FragmentContainerView สามารถจัดการได้อย่างน่าเชื่อถือ การเปลี่ยนแปลงธุรกรรมและการกำหนดค่า Fragment รายการ ซึ่งอาจไม่ได้ผลเป็น จะเกิดขึ้นหากคุณใช้แท็ก <fragment> แทน

รักษาการใช้งานอินสแตนซ์

มีการเปิดใช้การละเมิดการใช้งานอินสแตนซ์ของการเก็บรักษาโดยใช้ detectRetainInstanceUsage() และส่งข้อความ RetainInstanceUsageViolation

การละเมิดนี้ระบุถึงการใช้งาน Fragment ที่เก็บรักษาไว้ โดยเฉพาะอย่างยิ่ง หาก มีการเรียกไปยัง setRetainInstance() หรือ getRetainInstance(), ซึ่งเลิกใช้งานแล้วทั้งคู่

แทนที่จะใช้วิธีการเหล่านี้เพื่อจัดการอินสแตนซ์ Fragment ที่เก็บรักษาไว้ เก็บสถานะร้านค้าใน ViewModel ที่จัดการเรื่องนี้ให้คุณ

ตั้งค่าคำแนะนำที่ปรากฏต่อผู้ใช้

เปิดใช้การละเมิดคำแนะนำที่มองเห็นได้โดยผู้ใช้ของ Set โดยใช้ detectSetUserVisibleHint() และส่งข้อความ SetUserVisibleHintViolation

การละเมิดนี้ระบุถึงการเรียก setUserVisibleHint() ซึ่งเลิกใช้งานแล้ว

หากคุณเรียกใช้วิธีการนี้ด้วยตนเอง ให้โทร setMaxLifecycle() แทน หากคุณลบล้างเมธอดนี้ ให้ย้ายลักษณะการทำงานไปยัง onResume() เมื่อผ่านใน true และ onPause() เมื่อผ่านใน false

การใช้งาน Fragment เป้าหมาย

มีการเปิดใช้การละเมิดการใช้งานเป้าหมาย Fragment ด้วย detectTargetFragmentUsage() และส่งข้อความ TargetFragmentUsageViolation

การละเมิดนี้ระบุถึงการเรียก setTargetFragment() getTargetFragment(), หรือ getTargetRequestCode() ซึ่งทั้งหมดเลิกใช้งานแล้ว แทนที่จะใช้วิธีการเหล่านี้ ให้ลงทะเบียน FragmentResultListener สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการส่งผลลัพธ์ โปรดดูส่งผลลัพธ์ระหว่าง ส่วนย่อย

คอนเทนเนอร์ Fragment ไม่ถูกต้อง

มีการเปิดใช้การละเมิดคอนเทนเนอร์ของ Fragment ที่ไม่ถูกต้องโดยใช้ detectWrongFragmentContainer() และส่งข้อความ WrongFragmentContainerViolation

การละเมิดนี้บ่งชี้ถึงการเพิ่ม Fragment ลงในคอนเทนเนอร์อื่นที่ไม่ใช่ FragmentContainerView เช่นเดียวกับการใช้แท็ก Fragment ธุรกรรมที่เป็นส่วนย่อยอาจไม่ทำงานตามที่คาดไว้ เว้นแต่จะโฮสต์ภายใน FragmentContainerView การใช้มุมมองคอนเทนเนอร์ยังช่วยแก้ปัญหาใน View API ที่ ทำให้เศษส่วนที่ใช้ภาพเคลื่อนไหวออกถูกวาดทับบนอื่นๆ ทั้งหมด ส่วนย่อย