Gỡ lỗi mảnh

Hướng dẫn này cho biết các công cụ mà bạn có thể sử dụng để gỡ lỗi các mảnh của bạn.

Ghi nhật ký FragmentManager

FragmentManagercó khả năng phát nhiều loại thông báo tới Logcat. Theo mặc định, tính năng này sẽ bị tắt để tránh gây ra tiếng ồn cho Logcat. Tuy nhiên, đôi khi các thông báo nhật ký này có thể giúp bạn khắc phục các sự cố xảy ra với các mảnh khác. FragmentManagerphát ra kết quả ý nghĩa nhất ở cấp độ nhật ký Debug (Gỡ lỗi) vàVerbose (Chi tiết).

Bạn có thể bật tính năng ghi nhật ký bằng lệnh adb shell:

adb shell setprop log.tag.FragmentManager DEBUG

Ngoài ra, bạn có thể bật tính năng ghi nhật ký chi tiết:

adb shell setprop log.tag.FragmentManager VERBOSE

Nếu bật tính năng ghi nhật ký chi tiết, bạn có thể áp dụng bộ lọc cấp nhật ký trong cửa sổ Logcat. Tuy nhiên, thao tác này sẽ lọc tất cả nhật ký, chứ không chỉ lọc nhật ký FragmentManager. Thông thường, tốt nhất bạn chỉ bật tính năng ghi nhật ký FragmentManager ở cấp độ nhật ký mà bạn cần.

Ghi nhật ký DEBUG (Gỡ lỗi)

Ở cấp độ DEBUG, FragmentManager thường phát ra thông báo nhật ký liên quan đến các thay đổi về trạng thái của vòng đời. Mỗi mục nhật ký chứa tệp kết xuất toString() từ Fragment. Mục nhập nhật ký bao gồm các thông tin sau:

  • Tên lớp đơn giản của bản sao Fragment.
  • Mã băm danh tính của bản sao Fragment.
  • Mã nhận dạng duy nhất của FragmentManager của bản sao Fragment. Mã này ổn định khi người dùng thay đổi cấu hình và khi quy trình không còn (dead) và được tạo lại.
  • Mã của vùng chứa nơi Fragment được thêm vào nhưng chỉ khi đã được đặt.
  • Thẻ Fragment, nhưng chỉ khi được đặt.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)

Bạn có thể nhận dạng Mảnh từ:

  • Lớp FragmentNavHostFragment.
  • Mã băm danh tính là 92d8f1d.
  • Mã nhận dạng duy nhất là fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • Mã vùng chứa là 0x7f080116.
  • Thẻ này bị bỏ qua do bạn chưa đặt thẻ nào. Nếu có, mã này sẽ tuân theo mã ở định dạng tag=tag_value.

Để ngắn gọn và dễ đọc, các UUID đã được rút ngắn trong các ví dụ sau.

Ở đây chúng ta thấy một NavHostFragment được khởi chạy, sau đó startDestination Fragment loại FirstFragment được tạo và chuyển đổi sang trạng thái 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)

Sau khi người dùng tương tác, chúng ta thấy FirstFragment chuyển đổi sang các trạng thái khác nhau của vòng đời. Sau đó, SecondFragment được tạo bản sao và chuyển đổi sang trạng thái 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)

Tất cả các bản sao Mảnh đều được thêm một giá trị nhận dạng vào hậu tố để bạn có thể theo dõi các bản sao khác nhau của một lớp Fragment.

Ghi nhật ký VERBOSE (Chi tiết)

Ở cấp độ VERBOSE, FragmentManager thường phát ra thông báo nhật ký về trạng thái nội bộ:

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)

Thông báo này chỉ đề cập việc tải trên FirstFragment. Đưa chuyển đổi sang SecondFragment vào sẽ làm tăng đáng kể các mục nhập nhật ký. Hầu hết các thông báo nhật ký ở cấp VERBOSE đều được sử dụng ít hoặc không được sử dụng bởi các nhà phát triển ứng dụng. Tuy nhiên, việc thấy được thời điểm xảy ra các thay đổi đối với ngăn xếp lui có thể giúp gỡ lỗi một số vấn đề.

StrictMode cho các mảnh

Phiên bản 1.4.0-alpha01 của thư viện Jetpack Fragment tạo StrictMode cho các mảnh. Nó có thể phát hiện một số vấn đề thường gặp có thể khiến ứng dụng của bạn hoạt động theo cách không mong muốn.

Theo mặc định, mảnh StrictMode có chính sách LAX không lưu trữ bất kỳ giá trị nào. Tuy nhiên, bạn có thể tạo các chính sách tùy chỉnh. Policy tùy chỉnh xác định lỗi vi phạm được phát hiện và cho biết hình phạt nào sẽ được áp dụng khi phát hiện các lỗi vi phạm.

Để áp dụng một chính sách StrictMode tùy chỉnh, hãy chỉ định chính sách này cho FragmentManager. Bạn nên làm việc này càng sớm càng tốt. Trong trường hợp này, bạn thực hiện việc này ở khối init hoặc trong hàm khởi tạo 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());
        ...
   }
}

Đối với các trường hợp bạn cần biết Context để xác định xem có bật chế độ nghiêm ngặt hay không (ví dụ: từ giá trị của tài nguyên boolean), bạn có thể trì hoãn chỉ định một chính sách StrictMode cho FragmentManager bằng cách sử dụng 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());
        ...
   }
}

Điểm gần nhất mà bạn nên định cấu hình chế độ nghiêm ngặt để phát hiện mọi vi phạm có thể xảy ra là ở onCreate(), nhưng tại đây, bạn phải định cấu hình chế độ nghiêm ngặt trước khi gọi đến 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());
        ...
   }
}

Chính sách dùng trong những ví dụ này chỉ phát hiện các lỗi vi phạm tái sử dụng mảnh và ứng dụng sẽ chấm dứt bất cứ khi nào xảy ra lỗi. penaltyDeath() có thể hữu ích trong các bản gỡ lỗi bởi nó hỏng (fail) đủ nhanh để bạn không thể bỏ qua các lỗi vi phạm.

Bạn cũng có thể chọn cho phép một số vi phạm nhất định. Tuy nhiên, trong ví dụ này, chính sách này thực thi lỗi vi phạm này cho tất cả các loại phân đoạn khác. Điều này hữu ích cho các trường hợp trong đó thành phần thư viện bên thứ ba có thể chứa các vi phạm StrictMode. Trong các trường hợp đó, bạn có thể tạm thời thêm các lỗi vi phạm đó vào danh sách cho phép của StrictMode cho các thành phần mà bạn không sở hữu cho đến khi thư viện khắc phục lỗi vi phạm đó.

Hãy xem tài liệu về FragmentStrictMode.Policy.Builder để biết thông tin chi tiết về cách định cấu hình các lỗi vi phạm khác.

Có ba loại hình phạt.

  • penaltyLog() đổ thông tin chi tiết về lỗi vi phạm vào LogCat
  • penaltyDeath() chấm dứt ứng dụng khi phát hiện thấy lỗi vi phạm.
  • penaltyListener() cho phép bạn thêm trình nghe tùy chỉnh. Trình nghe này sẽ được gọi bất cứ khi nào phát hiện có lỗi vi phạm.

Bạn có thể áp dụng mọi tổ hợp hình phạt trong Policy của mình. Nếu chính sách của bạn không chỉ định rõ ràng hình phạt, hệ thống sẽ áp dụng chế độ mặc định là penaltyLog(). Nếu bạn áp dụng một hình phạt không phải penaltyLog() trong Policy tùy chỉnh, thì penaltyLog() sẽ bị tắt trừ khi bạn cài đặt cụ thể.

penaltyListener() có thể hữu ích khi bạn có thư viện ghi nhật ký bên thứ ba mà bạn muốn sử dụng để ghi nhật ký lỗi vi phạm. Hoặc bạn nên bật tính năng phát hiện vi phạm không nghiêm trọng trong bản phát hành và ghi chúng vào một thư viện báo cáo lỗi. Chiến lược này có thể phát hiện các hành vi vi phạm trong tự nhiên.

Tốt nhất bạn nên đặt chính sách StrictMode chung bằng cách đặt một chính sách mặc định áp dụng cho tất cả các bản sao FragmentManager thông qua 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());
    }
}

Phần sau đây sẽ mô tả các loại lỗi vi phạm và cách giải quyết có thể áp dụng.

Tái sử dụng mảnh

Lỗi vi phạm tái sử dụng mảnh được bật bằng cách sử dụng detectFragmentReuse() và gửi FragmentReuseViolation.

Lỗi vi phạm này cho biết việc tái sử dụng một phiên bản Fragment sau khi xóa nó khỏiFragmentManager. Việc tái sử dụng này có thể gây ra vấn đề vì Fragment có thể giữ lại trạng thái từ lần sử dụng trước và không hoạt động nhất quán. Nếu mỗi lần bạn tạo một bản sao mới, bản sao đó sẽ luôn ở trạng thái ban đầu khi được thêm vào FragmentManager.

Cách sử dụng thẻ mảnh

Lỗi sử dụng thẻ của mảnh được kích hoạt bằng cách dùng detectFragmentTagUsage() và gửi FragmentTagUsageViolation.

Lỗi này cho thấy Fragment đã bị tăng cường bằng cách sử dụng thẻ <fragment> trong bố cục XML. Để giải quyết vấn đề này, hãy tăng cường Fragment của bạn trong <androidx.fragment.app.FragmentContainerView> thay vì trong thẻ <fragment>. Các phân đoạn được tăng cường bằng cách sử dụng FragmentContainerView có thể xử lý đáng tin cậy các thay đổi về giao dịch và cấu hình Fragment. Các thẻ này có thể không hoạt động như mong đợi nếu bạn sử dụng thẻ <fragment>.

Cách sử dụng phiên bản giữ lại

Lỗi sử dụng phiên bản giữ lại được bật bằng cách sử dụng detectRetainInstanceUsage() và gửi RetainInstanceUsageViolation.

Lỗi vi phạm này cho biết việc sử dụng Fragment được giữ lại. Cụ thể, nếu có các lệnh gọi setRetainInstance() hoặc getRetainInstance() đều không còn được dùng nữa.

Thay vì sử dụng các phương thức này để tự quản lý các bản sao Fragment được giữ lại, bạn nên lưu trữ trạng thái trong ViewModel giúp bạn xử lý vấn đề này.

Đặt gợi ý trực quan cho người dùng

Lỗi vi phạm gợi ý hiển thị do người dùng đặt đã được bật bằng cách sử dụng detectSetUserVisibleHint() và gửi SetUserVisibleHintViolation.

Lỗi vi phạm này cho biết một lệnh gọi đến setUserVisibleHint() sẽ không được dùng nữa.

Nếu đang gọi phương thức này theo cách thủ công, bạn nên gọi setMaxLifecycle(). Nếu ghi đè phương thức này, bạn nên di chuyển hành vi sang onResume() khi chuyển vào trueonPause() khi chuyển vảo false.

Sử dụng mảnh mục tiêu

Lỗi vi phạm cách sử dụng mảnh mục tiêu được kích hoạt bằng cách sử dụng detectTargetFragmentUsage() và gửi TargetFragmentUsageViolation.

Lỗi vi phạm này cho biết lệnh gọi đến setTargetFragment(), getTargetFragment() hoặc getTargetRequestCode() đã ngừng hoạt động. Thay vì sử dụng các phương thức này, bạn nên đăng ký FragmentResultListener. Để biết thêm thông tin, xem phần Chuyển kết quả giữa các mảnh.

Vùng chứa mảnh không đúng

Lỗi vi phạm vùng chứa mảnh được kích hoạt bằng cách sử dụng detectWrongFragmentContainer()và gửi WrongFragmentContainerViolation.

Lỗi vi phạm này cho biết đã thêm Fragment vào một vùng chứa không phải là FragmentContainerView. Như với Cách sử dụng thẻ mảnh, Giao dịch mảnh có thể không hoạt động như mong đợi trừ khi được lưu trữ trong FragmentContainerView. Thao tác này cũng giúp giải quyết một vấn đề trong API View vốn khiến các mảnh sử dụng hình động thoát được vẽ ngoài các mảnh khác.