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

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

تسجيل 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 في التطبيق إلا بشكل ضئيل المطورين. ومع ذلك، فإن معرفة وقت حدوث التغييرات على المكدس الخلفي يمكن أن يساعد في وتصحيح بعض الأخطاء.

الوضع الصارم للأجزاء

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

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

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

لضبط سياسة StrategyMode العامة، يجب ضبط سياسة تلقائية تنطبق على جميع مثيلان (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 API. إلى رسم الأجزاء التي تستخدم الرسوم المتحركة عند الخروج فوق كل الأجزاء.