Gỡ lỗi mảnh

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

Ghi nhật ký FragmentManager

FragmentManager có thể phát đi 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. Tuy nhiên, đôi khi những thông điệp nhật ký này có thể giúp bạn khắc phục sự cố xảy ra với các mảnh. FragmentManager phát đi đầu ra ý nghĩa nhất ở cấp độ nhật ký DEBUGVERBOSE.

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

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 như sau:

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à mình cần.

Ghi nhật ký DEBUG

Ở cấp độ DEBUG, FragmentManager thường phát đi thông điệp nhật ký liên quan đến các thay đổi về trạng thái vòng đời. Mỗi mục nhập 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 thực thể Fragment.
  • Mã nhận dạng duy nhất của trình quản lý mảnh của thực thể Fragment. Mã này ổn định khi cấu hình thay đổi cũng như trong trường hợp bị buộc tắt và tạo lại.
  • Mã nhận dạng 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ưới đây là mục nhập nhật ký DEBUG mẫu:

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • 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ó, thẻ này sẽ theo sau mã nhận dạng ở định dạng tag=tag_value.

Để ngắn gọn và dễ đọc, các mã nhận dạng duy nhất (UUID) được rút ngắn trong những ví dụ sau.

Ở đây là NavHostFragment được khởi động, sau đó startDestination Fragment thuộc loại FirstFragment được tạo và chuyển đổi cho đến 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, FirstFragment sẽ chuyển đổi qua các trạng thái khác nhau của vòng đời. Sau đó, SecondFragment được tạo thực thể và chuyển đổi cho đến 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)

Mọi thực thể Fragment đều được thêm một giá trị nhận dạng vào phía sau để bạn có thể theo dõi các thực thể khác nhau của cùng một lớp Fragment.

Ghi nhật ký VERBOSE

Ở cấp độ VERBOSE, FragmentManager thường phát đi thông điệp 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)

Ví dụ này chỉ đề cập đến hoạt động tải trên FirstFragment. Nếu thêm hoạt động chuyển đổi vào SecondFragment, các mục nhập nhật ký sẽ tăng lên đáng kể. Nhiều thông điệp nhật ký ở cấp độ VERBOSE ít khi được nhà phát triển ứng dụng dùng đến. Tuy nhiên, việc thấy được thời điểm xảy ra thay đổi đối với ngăn xếp lui có thể giúp gỡ lỗi cho một số vấn đề.

StrictMode cho các mảnh

Phiên bản 1.4.0 trở lên của thư viện Jetpack Fragment bao gồm StrictMode cho các mảnh. Nó phát hiện được 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. Để biết thêm thông tin về cách làm việc với StrictMode, hãy xem bài viết StrictMode.

Policy tuỳ 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 tuỳ chỉnh, hãy chỉ định chính sách này cho FragmentManager. Hãy thực hiện 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 những trường hợp mà bạn cần phải biết Context để xác định xem có bật StrictMode hay không, chẳng hạn như từ giá trị của tài nguyên boolean, bạn có thể trì hoãn việc 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 mới nhất mà bạn có thể định cấu hình để StrictMode phát hiện mọi vi phạm có thể xảy ra là ở onCreate(), trước lệnh 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 về sử dụng lại 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 vì nó bị lỗi đủ nhanh để bạn không thể bỏ qua lỗi vi phạm.

Bạn cũng có thể chọn cho phép một số lỗi vi phạm nhất định. Tuy nhiên, chính sách dùng trong ví dụ trước sẽ thực thi lỗi vi phạm này đối với tất cả các loại mảnh khác. Việc này hữu ích cho các trường hợp mà thành phần thư viện bên thứ ba có thể chứa các lỗi vi phạm StrictMode.

Trong những trường hợp như vậy, 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 đối với các thành phần mà bạn không sở hữu cho đến khi thư viện khắc phục được lỗi vi phạm đó.

Để 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, hãy xem tài liệu về FragmentStrictMode.Policy.Builder.

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

  • penaltyLog() kết xuất 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 tuỳ chỉnh được gọi mỗi khi phát hiện 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 tuỳ chỉnh, thì penaltyLog() sẽ bị tắt trừ phi bạn đặt rõ ràng.

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 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 lỗi vi phạm bị bỏ lỡ.

Để đặt chính sách StrictMode chung, hãy đặt một chính sách mặc định áp dụng cho tất cả các thực thể FragmentManager bằng phương thức 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 sử dụng lại một thực thể Fragment sau khi nó bị xoá khỏi FragmentManager. Việc sử dụng lại 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à hoạt động không nhất quán. Nếu bạn tạo một thực thể mới mỗi lần, thực thể đó sẽ luôn ở trạng thái ban đầu khi được thêm vào FragmentManager.

Việc sử dụng thẻ mảnh

Lỗi vi phạm về việc sử dụng thẻ mảnh được bật bằng cách dùng detectFragmentTagUsage() và trả về FragmentTagUsageViolation.

Lỗi vi phạm 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 mảnh đượ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 mảnh này có thể hoạt động không như mong đợi nếu bạn sử dụng thẻ <fragment>.

Việc sử dụng thực thể được giữ lại

Lỗi vi phạm về việc sử dụng thực thể được giữ lại được bật bằng cách sử dụng detectRetainInstanceUsage() và trả về RetainInstanceUsageViolation.

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

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

Đặ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() không dùng nữa.

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

Việc 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(), tất cả đều không dùng nữa. Thay vì sử dụng các phương thức này, hãy đăng ký FragmentResultListener. Để biết thêm thông tin về cách chuyển kết quả, hãy 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. Giống với việc sử dụng thẻ Fragment, các giao dịch mảnh có thể hoạt động không như mong đợi trừ phi được lưu trữ bên trong FragmentContainerView. Việc sử dụng khung hiển thị vùng chứa 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 ảnh động thoát được vẽ ngoài các mảnh khác.