اشکال‌زدایی قطعه‌ها

این راهنما ابزارهایی را پوشش می‌دهد که می‌توانید برای اشکال‌زدایی قطعه‌های خود استفاده کنید.

گزارش‌گیری 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() داده‌های تخلیه‌شده از Fragment است. ورودی گزارش شامل اطلاعات زیر است:

  • نام کلاس ساده نمونه Fragment.
  • کد درهم‌سازی هویت نمونه 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 از حالت‌های مختلف چرخه حیات خارج می‌شود. سپس 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 برای قطعه‌ها

نسخه ۱.۴.۰ و بالاتر کتابخانه Jetpack Fragment شامل StrictMode برای قطعه‌ها است. این آزمایش می‌تواند برخی‌از مشکلات رایج را که ممکن است باعث شود برنامه‌تان رفتارهای غیرمنتظره داشته باشد شناسایی کند. برای اطلاعات بیشتر درباره کار با StrictMode، StrictMode را ببینید.

Policy سفارشی تعیین می‌کند کدام نقض‌ها شناسایی شوند و مشخص می‌کند درصورت شناسایی نقض‌ها، چه جریمه‌ای اعمال شود.

برای اعمال خط‌مشی StrictMode سفارشی، آن را به FragmentManager اختصاص دهید. این کار را در اسرع وقت انجام دهید. در این حالت، این کار را در یک بلوک init یا در سازنده جاوا انجام می‌دهید:

کاتلین

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)
        ...
   }
}

جاوا

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 به تعویق بیندازید:

کاتلین

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)
        ...
   }
}

جاوا

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() است:

کاتلین

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)
        ...
   }
}

جاوا

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() می‌تواند در ساخت‌های اشکال‌زدایی مفید باشد زیرا به‌اندازه کافی سریع ازکار می‌افتد که نمی‌توانید نقض‌ها را نادیده بگیرید.

همچنین ممکن است برخی‌از نقض‌ها به‌طور گزینشی مجاز شوند. بااین‌حال، خط‌مشی استفاده‌شده در مثال قبلی این نقض را برای همه انواع قطعه‌های دیگر اعمال می‌کند. این برای مواردی مفید است که مؤلفه کتابخانه طرف سوم ممکن است حاوی نقض‌های StrictMode باشد.

در چنین مواردی، می‌توانید موقتاً آن نقض‌ها را به فهرست مجاز StrictMode خود برای مؤلفه‌هایی که مالک آن‌ها نیستید اضافه کنید تا زمانی که کتابخانه نقض آن‌ها را برطرف کند.

برای جزئیات مربوط به نحوه پیکربندی موارد نقض دیگر، به اسناد FragmentStrictMode.Policy.Builder مراجعه کنید.

سه نوع جریمه وجود دارد.

  • penaltyLog() جزئیات نقض‌ها را در Logcat تخلیه می‌کند.
  • penaltyDeath() درصورت شناسایی نقض، برنامه را خاتمه می‌دهد.
  • penaltyListener() به شما امکان می‌دهد شنونده سفارشی اضافه کنید که هرگاه نقضی شناسایی شود، فراخوانده می‌شود.

می‌توانید هر ترکیبی از جریمه‌ها را در Policy خود اعمال کنید. اگر خط‌مشی شما به‌طور صریح جریمه‌ای را مشخص نکرده باشد، جریمه پیش‌فرض penaltyLog() اعمال می‌شود. اگر جریمه‌ای غیراز penaltyLog() در Policy سفارشی‌تان اعمال کنید، penaltyLog() غیرفعال می‌شود مگراینکه آن را به‌طور صریح تنظیم کنید.

penaltyListener() می‌تواند زمانی مفید باشد که کتابخانه ثبت گزارش طرف سومی دارید که می‌خواهید موارد نقض را در آن ثبت کنید. یا شاید بخواهید گرفتن نقض غیرکشنده را در ساخت‌های نسخه پخش فعال کنید و آن‌ها را در کتابخانه گزارش خرابی ثبت کنید. این استراتژی می‌تواند موارد نقضی را که ممکن است نادیده گرفته شوند شناسایی کند.

برای تنظیم خط‌مشی StrictMode سراسری، خط‌مشی پیش‌فرضی تنظیم کنید که بااستفاده از روش FragmentStrictMode.setDefaultPolicy() بر همه نمونه‌های FragmentManager اعمال شود:

کاتلین

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()
    }
}

جاوا

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 را به‌جای برچسب <fragment> در <androidx.fragment.app.FragmentContainerView> گسترش دهید. قطعاتی که بااستفاده از 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 میزبانی شوند. استفاده از نمای محفظه همچنین به رفع مشکلی در View API کمک می‌کند که باعث می‌شود قطعاتی که از پویانمایی‌های خروج استفاده می‌کنند، روی همه قطعات دیگر کشیده شوند.