التعامل مع التغييرات في الإعدادات

ويمكن أن تتغير بعض إعدادات الأجهزة أثناء تشغيل التطبيق. وهذه تتضمن، على سبيل المثال لا الحصر:

  • حجم عرض التطبيق
  • اتجاه الشاشة
  • حجم الخط وسُمكه
  • اللغة
  • الوضع الداكن مقابل الوضع الفاتح
  • توفُّر لوحة المفاتيح

تحدث معظم تغييرات الضبط هذه بسبب بعض تفاعلات المستخدم. على سبيل المثال، يؤدي تدوير الجهاز أو طيه إلى تغيير مقدار مساحة الشاشة المتاحة لتطبيقك. وبالمثل، يؤدي تغيير إعدادات الجهاز، مثل حجم الخط أو اللغة أو المظهر المفضَّل إلى تغيير قيمها في الكائن Configuration.

تتطلب هذه المعلمات عادةً إجراء تغييرات كبيرة بما يكفي على واجهة المستخدم للتطبيق بحيث يكون لدى نظام Android الأساسي آلية مصممة لغرض معين عند تغييرها. هذه الآلية هي إعادة إنشاء Activity.

التسلية في الأنشطة

يُعيد النظام إنشاء Activity عند حدوث تغيير في الإعدادات. لإجراء ذلك، يتصل النظام بـ onDestroy() ويحذف مثيل Activity الحالي. بعد ذلك، يتم إنشاء مثيل جديد باستخدام onCreate()، ويتم إعداد مثيل Activity الجديد هذا باستخدام الإعدادات الجديدة المعدّلة. يعني ذلك أيضًا أنّ النظام سيعيد إنشاء واجهة المستخدم باستخدام الإعدادات الجديدة.

يساعد سلوك إعادة التحميل تطبيقك على التكيّف مع الإعدادات الجديدة من خلال إعادة تحميل تطبيقك تلقائيًا باستخدام موارد بديلة تتطابق مع إعدادات الجهاز الجديدة.

مثال للترفيه

يمكنك استخدام TextView يعرض عنوانًا ثابتًا باستخدام android:text="@string/title" على النحو المحدّد في ملف XML للتنسيق. عند إنشاء طريقة العرض، يتم تعيين النص مرة واحدة فقط، بناءً على اللغة الحالية. وإذا تغيّرت اللغة، يعيد النظام إنشاء النشاط. وبالتالي، يُعيد النظام إنشاء طريقة العرض ويضبطها على القيمة الصحيحة بناءً على اللغة الجديدة.

تؤدي عملية إعادة الإنشاء أيضًا إلى محو أي حالة تم الاحتفاظ بها كحقول في Activity أو في أي من Fragment أو View أو أي كائنات أخرى مضمّنة فيها. ويرجع ذلك إلى أنّ إعادة إنشاء Activity تنشئ مثيلاً جديدًا تمامًا للرمز Activity وواجهة المستخدم. بالإضافة إلى ذلك، لم يعُد Activity القديم مرئيًا أو صالحًا، وبالتالي أي مراجع متبقية له أو إلى عناصره المضمَّنة قديمة. حيث يمكن أن تتسبب في حدوث أخطاء وتسرّب للذاكرة وأعطال.

توقعات المستخدم

يتوقع مستخدم التطبيق الحفاظ على الحالة. إذا ملأ المستخدم نموذجًا وفتح تطبيقًا آخر في وضع النوافذ المتعددة لعرض المعلومات المرجعية، ستكون تجربة المستخدم سيئة إذا عاد إلى نموذج تم محوه أو انتقل إلى مكان آخر في التطبيق بالكامل. وبصفتك مطوِّرًا، عليك توفير تجربة مستخدم متسقة من خلال إجراء تغييرات على الإعدادات وإعادة إنشاء الأنشطة.

للتحقّق مما إذا تم الاحتفاظ بالحالة في تطبيقك، يمكنك تنفيذ إجراءات تؤدي إلى تغييرات في الإعدادات أثناء تشغيل التطبيق في المقدّمة وأثناء تشغيله في الخلفية. وتتضمن هذه الإجراءات:

  • تدوير الجهاز
  • جارٍ الدخول إلى وضع النوافذ المتعددة
  • تغيير حجم التطبيق أثناء وضع النوافذ المتعددة أو النافذة الحرة
  • طي جهاز قابل للطي يضم شاشات متعددة
  • تغيير مظهر النظام، مثل الوضع الداكن مقابل الوضع الفاتح
  • تغيير حجم الخط
  • تغيير لغة النظام أو التطبيق
  • توصيل لوحة مفاتيح خارجية أو فصلها
  • توصيل قاعدة الإرساء أو فصلها

هناك ثلاث مناهج أساسية يمكنك اتباعها للحفاظ على الحالة ذات الصلة من خلال إعادة إنشاء Activity. ويعتمد الخيار الذي يمكن استخدامه على نوع الحالة التي تريد الاحتفاظ بها:

  • الثبات المحلي لمعالجة توقُّف العملية للبيانات المعقّدة أو الكبيرة تتضمن مساحة التخزين المحلية الدائمة قواعد البيانات أو DataStore.
  • العناصر التي تم الاحتفاظ بها، مثل مثيلات ViewModel، لمعالجة الحالة المتعلقة بواجهة المستخدم في الذاكرة أثناء استخدام المستخدم للتطبيق بشكل نشط.
  • حالة المثيل المحفوظة للتعامل مع وفاة العملية التي يبدأها النظام وإبقاء الحالة العابرة التي تعتمد على مدخلات المستخدم أو تنقله.

للقراءة عن واجهات برمجة التطبيقات لكلٍ من هذه واجهات برمجة التطبيقات بالتفصيل، وعندما يكون من المناسب استخدام كل منها، يمكنك الاطّلاع على حفظ حالات واجهة المستخدم.

فرض قيود على الأنشطة الترفيهية

يمكنك منع إعادة إنشاء النشاط تلقائيًا لإجراء تغييرات معيّنة على الإعدادات. تؤدي إعادة إنشاء Activity إلى إعادة إنشاء واجهة المستخدم بالكامل وأي عناصر مشتقة من Activity. قد تكون لديك أسباب وجيهة لتجنّب حدوث ذلك. على سبيل المثال، قد لا يحتاج تطبيقك إلى تحديث الموارد أثناء تغيير إعداد معيّن، أو قد تكون هناك قيود على الأداء. في هذه الحالة، يمكنك توضيح أنّ نشاطك يعالج تغيير الإعدادات نفسه ومنع النظام من إعادة تشغيل نشاطك.

لإيقاف إعادة إنشاء الأنشطة لتغييرات معيّنة على الإعدادات، أضِف نوع الضبط إلى android:configChanges في الإدخال <activity> في ملف AndroidManifest.xml. تظهر القيم المحتملة في المستندات الخاصة بالسمة android:configChanges.

يوقِف رمز البيان التالي إعادة إنشاء Activity للنطاق MyActivity عند تغيير اتجاه الشاشة ومدى توفّر لوحة المفاتيح:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

تؤدي بعض تغييرات الضبط دائمًا إلى إعادة تشغيل النشاط. ولا يمكنك إيقافها. على سبيل المثال، لا يمكنك إيقاف تغيير الألوان الديناميكية الذي تم طرحه في Android 12L (المستوى 32 لواجهة برمجة التطبيقات).

التفاعل مع تغييرات الإعدادات في نظام العرض

في نظام View، عندما يحدث تغيير في الإعدادات وأوقفت إعادة إنشاء Activity، يتلقّى النشاط مكالمة إلى Activity.onConfigurationChanged(). تتلقّى أيضًا أي ملفات شخصية مرفقة مكالمة أيضًا إلى View.onConfigurationChanged(). بالنسبة إلى تغييرات الإعدادات التي لم تضِفها إلى android:configChanges، يُعيد النظام إنشاء النشاط كالعادة.

تتلقّى طريقة معاودة الاتصال onConfigurationChanged() كائن Configuration يحدد إعدادات الجهاز الجديدة. اقرأ الحقول في الكائن Configuration لتحديد الإعدادات الجديدة. ولإجراء التغييرات اللاحقة، يمكنك تعديل الموارد التي تستخدمها في واجهتك. عندما يستدعي النظام هذه الطريقة، يتم تعديل كائن Resources في نشاطك لعرض الموارد بناءً على الإعدادات الجديدة. يتيح لك هذا إعادة تعيين عناصر واجهة المستخدم دون أن يعيد النظام تشغيل نشاطك.

على سبيل المثال، يتحقّق تطبيق onConfigurationChanged() التالي من توفّر لوحة مفاتيح:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

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

الحفاظ على الحالة

عند استخدام هذا الأسلوب، يجب أن تحتفظ بحالته أثناء دورة حياة النشاط العادي. ويرجع ذلك إلى ما يلي:

  • تغييرات لا يمكن تجنبها: تؤدي تغييرات الضبط التي لا يمكنك منعها إلى إعادة تشغيل تطبيقك.
  • انتهاء العملية: يجب أن يكون تطبيقك قادرًا على التعامل مع انتهاء العملية التي يبدأها النظام. إذا غادر المستخدم تطبيقك ثم انتقل إلى الخلفية، قد يدمر النظام التطبيق.
.

التفاعل مع تغييرات الإعدادات في Jetpack Compose

تتيح Jetpack Compose لتطبيقك التفاعل بسهولة أكبر مع تغييرات الإعدادات. ومع ذلك، إذا أوقفت إعادة إنشاء Activity لجميع تغييرات الإعدادات حيثما أمكن، سيظل على تطبيقك معالجة تغييرات الضبط بشكل صحيح.

يتوفر الكائن Configuration في التسلسل الهرمي لواجهة مستخدم Compose مع عنصر LocalConfiguration المحلي. وعند تغييرها، تتم إعادة إنشاء الدوال القابلة للإنشاء من LocalConfiguration.current. للحصول على معلومات حول طريقة عمل مقطوعات موسيقية محلّية، يمكنك الاطّلاع على البيانات ذات النطاق المحلي باستخدام SurfaceLocal.

مثال

في المثال التالي، يعرض عنصر قابل للإنشاء تاريخًا بتنسيق معيّن. يتفاعل العنصر القابل للإنشاء مع التغييرات في إعدادات لغة النظام من خلال طلب الرمز ConfigurationCompat.getLocales() باستخدام LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

لتجنُّب إعادة إنشاء Activity عند تغيير اللغة، على Activity التي تستضيف رمز الإنشاء إيقاف تغييرات إعدادات اللغة. لإجراء ذلك، يمكنك ضبط android:configChanges على locale|layoutDirection.

تغييرات الإعدادات: المفاهيم الأساسية وأفضل الممارسات

هذه هي المفاهيم الأساسية التي تحتاج إلى معرفتها عند العمل على تغييرات التهيئة:

  • الإعدادات: تحدِّد إعدادات الجهاز طريقة عرض واجهة المستخدم للمستخدم، مثل حجم عرض التطبيق أو اللغة أو مظهر النظام.
  • تغييرات الإعدادات: تتغير عمليات الضبط من خلال تفاعل المستخدم. على سبيل المثال، يمكن للمستخدم تغيير إعدادات الجهاز أو كيفية تفاعله فعليًا مع الجهاز. وليس هناك أي طريقة لمنع تغييرات الضبط.
  • إعادة إنشاء Activity: تؤدي تغييرات الإعدادات إلى إعادة إنشاء Activity تلقائيًا. هذه آلية مدمجة لإعادة تهيئة حالة التطبيق للضبط الجديد.
  • تدمير Activity: تؤدي إعادة إنشاء Activity إلى تدمير مثيل Activity القديم وإنشاء مثيل جديد بدلاً منه. المثيل القديم أصبح قديمًا الآن. تؤدي أي مراجع متبقية إليه إلى حدوث تسرب للذاكرة أو أخطاء أو أعطال.
  • الحالة: في مثيل Activity القديم غير موجودة في المثيل Activity الجديد، لأنهما مثيلان مختلفان للكائن. حافظ على حالة التطبيق والمستخدم كما هو موضّح في حفظ حالات واجهة المستخدم.
  • الإيقاف: يُعد إيقاف إعادة إنشاء النشاط لنوع من تغيير الضبط تحسينًا محتملاً. يتطلب ذلك تحديث تطبيقك بشكل صحيح استجابةً للتهيئة الجديدة.

لتقديم تجربة جيدة للمستخدم، يُرجى اتّباع أفضل الممارسات التالية:

  • الاستعداد للتغييرات المتكررة في الإعدادات: لا تفترض أنّ تغييرات الضبط نادرة أو لا تحدث أبدًا، بغض النظر عن مستوى واجهة برمجة التطبيقات أو شكل الجهاز أو مجموعة أدوات واجهة المستخدم. عندما ينفِّذ مستخدم تغييرًا في الإعدادات، يتوقع تحديث التطبيقات ومواصلة العمل بشكل صحيح مع الإعدادات الجديدة.
  • المحافظة على الحالة: لا تفقد حالة المستخدم عند إعادة تحميل Activity. احتفِظ بالحالة كما هو موضّح في حفظ حالات واجهة المستخدم.
  • تجنُّب إيقاف الميزة كحل سريع: لا توقف إعادة إنشاء Activity كاختصار لتجنُّب فقدان الحالة. في حال إيقاف إعادة إنشاء النشاط، عليك الوفاء بوعدك بمعالجة التغيير، وسيظل بإمكانك إيقاف الحالة بسبب إعادة إنشاء Activity بسبب تغييرات أخرى في الإعدادات أو انتهاء العملية أو إغلاق التطبيق. يستحيل إيقاف استرداد Activity تمامًا. احتفِظ بالحالة كما هو موضّح في حفظ حالات واجهة المستخدم.
  • لا تتجنّب تغييرات الإعدادات: لا تفرض قيودًا على الاتجاه أو نسبة العرض إلى الارتفاع أو إمكانية إعادة التعديل لتجنُّب التغييرات في الإعدادات وإعادة استخدام Activity. وهذا يؤثر سلبًا على المستخدمين الذين يريدون استخدام التطبيق بطريقتهم المفضّلة.

التعامل مع تغييرات الإعدادات المستنِدة إلى الحجم

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

هناك نوعان عامان من تغييرات الحجم: كبيرة وغير مهمة. يتمثل التغيير المهم في الحجم عندما تنطبق مجموعة مختلفة من الموارد البديلة على الإعدادات الجديدة بسبب اختلاف في حجم الشاشة، مثل العرض أو الارتفاع أو أصغر عرض. وتتضمن هذه الموارد الموارد التي يعرّفها التطبيق بنفسه وتلك المتوفرة في أي من مكتباته.

فرض قيود على إعادة إنشاء الأنشطة لتغييرات الإعدادات المستندة إلى الحجم

عند إيقاف إعادة إنشاء Activity لتغييرات الإعدادات المستندة إلى الحجم، لن يُعيد النظام إنشاء Activity. بدلاً من ذلك، يتلقّى مكالمة إلى Activity.onConfigurationChanged(). تتلقّى أي ملفات شخصية مرفقة مكالمة فيديو مع View.onConfigurationChanged().

يتم إيقاف إعادة إنشاء Activity بسبب تغييرات الإعدادات المستندة إلى الحجم في حال توفُّر android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" في ملف البيان.

السماح بإعادة إنشاء النشاط لتغييرات الإعدادات المستندة إلى الحجم

في الإصدار Android 7.0 (مستوى واجهة برمجة التطبيقات 24) والإصدارات الأحدث، تحدث عملية إعادة إنشاء Activity فقط لتغييرات الإعدادات المستندة إلى الحجم إذا كان التغيير كبيرًا. في حال عدم إعادة النظام إنشاء Activity بسبب عدم كفاية الحجم، قد يطلب النظام Activity.onConfigurationChanged() وView.onConfigurationChanged() بدلاً من ذلك.

هناك بعض التنبيهات التي يجب ملاحظتها في ما يتعلق بالاستدعاءَين Activity وView عند عدم إعادة إنشاء Activity:

  • في إصدارات Android 11 (المستوى 30 لواجهة برمجة التطبيقات) إلى Android 13 (المستوى 33 لواجهة برمجة التطبيقات)، لن يتم استدعاء "Activity.onConfigurationChanged()".
  • هناك مشكلة معروفة تتمثّل في عدم استدعاء View.onConfigurationChanged() في بعض الحالات على Android 12L (المستوى 32 لواجهة برمجة التطبيقات) والإصدارات الأقدم من Android 13 (المستوى 33 لواجهة برمجة التطبيقات). لمزيد من المعلومات، يُرجى الاطّلاع على هذه المشكلة العلنية. وقد تمت معالجة هذه المشكلة في الإصدارات اللاحقة من نظام التشغيل Android 13 وAndroid 14.

بالنسبة إلى الرموز البرمجية التي تعتمد على الاستماع إلى تغييرات الإعدادات المستندة إلى الحجم، ننصحك باستخدام أداة View مع رمز تم تجاوزه View.onConfigurationChanged() بدلاً من الاعتماد على إعادة إنشاء Activity أو Activity.onConfigurationChanged().