التعامل مع تغييرات الضبط

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

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

تحدث معظم تغييرات الإعدادات هذه بسبب بعض تفاعلات المستخدم. على سبيل المثال، يؤدي تدوير الجهاز أو طيّه إلى تغيير مساحة الشاشة المتاحة لتطبيقك. وبالمثل، يؤدي تغيير إعدادات الجهاز مثل حجم الخط أو اللغة أو المظهر المفضّل إلى تغيير قيم كل منها في عنصر 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 في التدرج الهرمي لواجهة المستخدم عند إنشاء واجهة المستخدم مع توفُّر تركيبة LocalConfiguration المحلية. وعندما تتغير، تتم إعادة إنشاء الدوال القابلة للإنشاء التي تتم قراءتها من LocalConfiguration.current. للحصول على معلومات حول آلية عمل المقطوعات الموسيقية المحلية، يمكنك الاطّلاع على البيانات المحلية ذات النطاق المحلي باستخدام ProductionLocal.

مثال

في المثال التالي، تعرض مادة العرض القابلة للإنشاء تاريخًا بتنسيق محدّد. تتفاعل العناصر القابلة للإنشاء مع التغييرات في إعدادات لغة النظام من خلال استدعاء 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().