המדריך הזה עוסק בכלים שיכולים לשמש לניפוי באגים מקטעים.
רישום ביומן של 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()
.
Dump מ-Fragment
.
רשומה ביומן מורכבת מהפרטים הבאים:
- שם המחלקה הפשוט של המכונה
Fragment
. - קוד גיבוב (hash) של הזהות
של המופע
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
מועבר אל
למצבים השונים של מחזור החיים. לאחר מכן נוצר אובייקט (instantiation) ו-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
, 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
לא רלוונטיות לאפליקציה
למפתחים. אבל אם מתרחשים שינויים במקבץ האחורי, יכול להיות שזה יעזור
לנפות באגים בכמה בעיות.
StrictMode למקטעים
גרסה 1.4.0 ומעלה של
ספריית Jetpack Fragment כוללת
StrictMode למקטעים. הוא יכול לזהות כמה בעיות נפוצות שעלולות לגרום
האפליקציה תתנהג בדרכים בלתי צפויות. לקבלת מידע נוסף על עבודה עם
StrictMode
, ראו StrictMode.
בהתאמה אישית
Policy
מגדיר אילו הפרות יזוהו ומציין איזה עונש יופעל.
כשמזוהות הפרות.
כדי להחיל מדיניות StrictMode מותאמת אישית, מקצים אותה ל:
FragmentManager
מומלץ לעשות זאת בהקדם האפשרי. במקרה הזה, עושים את זה
בלוק init
או ב-constructor של 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()
יכול להיות
מועילות ב-builds של ניפוי באגים כי הן נכשלות מהר מספיק ואי אפשר להתעלם מהן.
הפרות.
אפשר גם להתיר באופן סלקטיבי הפרות מסוימות. המדיניות שבה נעשה שימוש אבל בדוגמה שלמעלה, אוכפים את ההפרה בכל המקטעים האחרים. שונים. האפשרות הזו שימושית במקרים שבהם רכיב ספרייה של צד שלישי מכילים הפרות StrictMode.
במקרים כאלה, אפשר להוסיף באופן זמני את ההפרות האלה לרשימת ההיתרים של ה-StrictMode עבור רכיבים שאינם בבעלותכם עד הספרייה מתקנים את ההפרה.
לקבלת פרטים על הגדרה של הפרות אחרות, עיינו במסמכי התיעוד של
FragmentStrictMode.Policy.Builder
קיימים שלושה סוגים של השלכות.
penaltyLog()
שומר ל-Logcat את פרטי ההפרות.penaltyDeath()
יפסיק את האפליקציה כאשר יזוהו הפרות.penaltyListener()
מאפשרת להוסיף האזנה מותאמת אישית שמופעלת בכל פעם שיש הפרות זוהה.
אפשר להחיל כל שילוב של עונשים בPolicy
. אם המדיניות
לא לציין במפורש את העונש, תחול ברירת המחדל של penaltyLog()
. אם
להחיל קנס שאינו penaltyLog()
בPolicy
בהתאמה אישית, ואז
penaltyLog()
מושבת אלא אם מגדירים אותו באופן מפורש.
penaltyListener()
יכול להיות שימושי כשיש לך ספריית רישום ביומן של צד שלישי כדי
שרוצים לתעד בהן הפרות. לחלופין, אולי כדאי להפעיל
הפרה לא חמורה שתיעדה את גרסאות ה-build ורשום אותן לדיווח על קריסה
לספרייה. השיטה הזו יכולה לזהות הפרות שאחרת יוחמצו.
כדי להגדיר מדיניות StrictMode גלובלית, צריך להגדיר מדיניות ברירת מחדל שתחול על כל
ב-FragmentManager
מופעים באמצעות הפונקציה
FragmentStrictMode.setDefaultPolicy()
method:
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()); } }
בקטעים הבאים מתוארים סוגי ההפרות ופתרונות אפשריים.
שימוש חוזר במקטעים
הפרת השימוש החוזר בקטע מופעלת באמצעות
detectFragmentReuse()
וזורקת
FragmentReuseViolation
ההפרה הזו מצביעה על שימוש חוזר במכונה של Fragment
לאחר ההסרה שלה
החל מ-FragmentManager
. השימוש החוזר הזה עלול לגרום לבעיות כי Fragment
עלול
לשמור את המצב מהשימוש הקודם בו ולא להתנהג באופן עקבי. אם תיצור
בכל מופע חדש, הוא תמיד במצב הראשוני כשמוסיפים אותו
FragmentManager
שימוש בתג מקטע
הפרת השימוש בתג מקטע מופעלת באמצעות
detectFragmentTagUsage()
וזורקת
FragmentTagUsageViolation
ההפרה הזו מצביעה על כך שמדד Fragment
מנופח באמצעות המאפיין <fragment>
בפריסת XML. כדי לפתור את הבעיה, צריך להגדיל את הFragment
בפנים
<androidx.fragment.app.FragmentContainerView>
ולא ב<fragment>
התיוג. מקטעים המנפחים את גודלם באמצעות FragmentContainerView
יכולים לטפל באופן מהימן
Fragment
עסקאות ושינויים בהגדרות. יכול להיות שהאפשרויות האלה לא יפעלו כמו
צפויה אם משתמשים בתג <fragment>
במקום זאת.
שמירת השימוש במכונה
הפרת השימוש במכונה לשמירה על תאימות מופעלת באמצעות
detectRetainInstanceUsage()
וזורקת
RetainInstanceUsageViolation
ההפרה הזו מציינת שימוש ב-Fragment
שנשמר, באופן ספציפי, אם
יש קריאות
setRetainInstance()
או
getRetainInstance()
,
ששניהם הוצאו משימוש.
במקום להשתמש בשיטות האלה כדי לנהל מכונות Fragment
שנשמרו
מאחסנים את המצב
ViewModel
שמטפל בזה בשבילכם.
הגדרת הרמז הגלוי למשתמש
הפרת המדיניות בנושא 'רמז גלוי למשתמש' מופעלת באמצעות
detectSetUserVisibleHint()
וזורקת
SetUserVisibleHintViolation
הפרה זו מציינת קריאה ל-
setUserVisibleHint()
שהוצא משימוש.
אם מבצעים קריאה ידנית לשיטה הזו, צריך להתקשר
setMaxLifecycle()
במקום זאת. אם משנים את שיטת הפעולה הזאת, צריך להעביר את ההתנהגות אל
onResume()
כשמעבירים ב-true
ו-
onPause()
כשמעבירים false
.
שימוש במקטע יעד
ההפרה של השימוש במקטע היעד מופעלת באמצעות
detectTargetFragmentUsage()
וזורקת
TargetFragmentUsageViolation
הפרה זו מציינת קריאה ל-
setTargetFragment()
getTargetFragment()
,
או getTargetRequestCode()
,
כולם הוצאו משימוש. במקום להשתמש בשיטות האלה, רשמו
FragmentResultListener
למידע נוסף על העברת תוצאות, אפשר לעיין בתוצאות מעבר בין הבדיקות
מקטעים.
מאגר מקטעים שגוי
הפרה שגויה של מאגר מקטעים מופעלת באמצעות
detectWrongFragmentContainer()
וזורקת
WrongFragmentContainerViolation
ההפרה הזו מציינת הוספה של Fragment
למאגר תגים שאינו
FragmentContainerView
. בדומה לשימוש בתג Fragment
,
ייתכן שעסקאות של קטעים לא יפעלו כצפוי אלא אם הן מתארחות בתוך
FragmentContainerView
שימוש בתצוגת קונטיינר גם עוזר לטפל בבעיה ב-API של View
,
גורם לכך שקטעים שמשתמשים באנימציות יציאה ייכתבו מעל
של קטעים שונים.