ניפוי באגים במקטעים

המדריך הזה עוסק בכלים שיכולים לשמש לניפוי באגים מקטעים.

רישום ביומן של 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, גורם לכך שקטעים שמשתמשים באנימציות יציאה ייכתבו מעל של קטעים שונים.