تصحيح أخطاء الأجزاء

يتناول هذا الدليل الأدوات التي يمكنك استخدامها لتصحيح أخطاء الأجزاء.

تسجيل 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 للأجزاء

يشتمل الإصدار 1.4.0 والإصدارات الأحدث من مكتبة Jetpack Fragment على وضع تقييد للأجزاء. ويمكنه اكتشاف بعض المشكلات الشائعة التي قد تتسبب في عمل تطبيقك بطرق غير متوقعة. لمزيد من المعلومات عن استخدام StrictMode، يمكنك الاطّلاع على StrictMode.

إنّ السمة Policy المخصّصة تحدّد الانتهاكات التي يتم رصدها وتحدّد العقوبة التي يتم فرضها عند رصد المخالفات.

لتطبيق سياسة حصرية مُخصّصة، يجب تعيينها إلى FragmentManager. ننصحك بإجراء ذلك في أقرب وقت ممكن. في هذه الحالة، يمكنك تنفيذ ذلك في كتلة init أو في الدالة الإنشائية لـ 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() مفيدًا في إنشاء إصدارات تصحيح الأخطاء لأنّه يفشل بسرعة بما فيه الكفاية لدرجة أنّه لا يمكنك تجاهل الانتهاكات.

من الممكن أيضًا السماح بانتهاكات معيّنة بشكل انتقائي. ومع ذلك، تفرض السياسة المستخدمة في المثال السابق هذا الانتهاك على جميع أنواع الأجزاء الأخرى. هذا مفيد في الحالات التي قد يتضمن فيها مكوِّن مكتبة جهة خارجية انتهاكات StrictMode.

وفي مثل هذه الحالات، يمكنك إضافة هذه الانتهاكات مؤقتًا إلى القائمة المسموح بها الخاصة بـ StrictMode للمكوّنات التي لا تمتلكها إلى أن يتم إصلاح الانتهاك في المكتبة.

للحصول على تفاصيل عن كيفية ضبط الانتهاكات الأخرى، اطّلِع على مستندات FragmentStrictMode.Policy.Builder.

ثمة ثلاثة أنواع من العقوبات.

  • يعرض penaltyLog() تفاصيل الانتهاكات إلى Logcat.
  • penaltyDeath() يؤدي إلى إنهاء التطبيق عند رصد المخالفات.
  • penaltyListener() تتيح لك إضافة أداة معالجة مخصّصة يتم استدعاؤها عندما يتم رصد انتهاكات.

يمكنك تطبيق أي مجموعة من العقوبات في Policy. إذا لم تحدّد السياسة عقوبة بشكل صريح، يتم تطبيق القيمة التلقائية penaltyLog(). إذا طبّقت عقوبة أخرى غير penaltyLog() في Policy المخصّص، سيتم إيقاف penaltyLog() ما لم تحدّده بشكل صريح.

يمكن أن تكون penaltyListener() مفيدة عندما تتوفّر لديك مكتبة تسجيل تابعة لجهة خارجية وتريد تسجيل الانتهاكات. بدلاً من ذلك، قد ترغب في تفعيل رصد المخالفات غير الفادحة في إصدارات الإصدارات وتسجيلها في مكتبة تقارير الأعطال. يمكن لهذه الاستراتيجية رصد الانتهاكات التي لم يتم رصدها.

لضبط سياسة StrictMode عمومية، يمكنك ضبط سياسة تلقائية تنطبق على جميع مثيلات FragmentManager باستخدام الطريقة 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());
    }
}

توضّح الأقسام التالية أنواع المخالفات والحلول الممكنة.

إعادة استخدام الأجزاء

يتم تفعيل انتهاك إعادة استخدام الجزء باستخدام 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. يساعد استخدام عرض الحاوية أيضًا في معالجة مشكلة في واجهة برمجة تطبيقات View تؤدّي إلى رسم الأجزاء التي تستخدم حركات الخروج فوق كل الأجزاء الأخرى.