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
FragmentManager
có 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. FragmentManager
phá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 saoFragment
. 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
Fragment
làNavHostFragment.
- 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àoLogCat
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 true
và onPause()
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.