هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.
مقدمة
في الدرس العملي السابق، تعرّفت على مراحل النشاط في Activity وFragment، واستكشفت الطرق التي يتم استدعاؤها عند تغيُّر حالة مراحل النشاط في الأنشطة واللقطات. في هذا الدرس التطبيقي العملي، ستستكشف دورة حياة النشاط بتفصيل أكبر. تتعرّف أيضًا على مكتبة دورة الحياة في Android Jetpack التي يمكن أن تساعدك في إدارة أحداث دورة الحياة باستخدام رمز برمجي منظَّم بشكل أفضل وأسهل في الصيانة.
ما يجب معرفته
- ما هو النشاط وكيفية إنشائه في تطبيقك
- أساسيات دورات حياة
ActivityوFragment، وعناصر معالجة البيانات التي يتم استدعاؤها عندما ينتقل نشاط بين الحالات - كيفية إلغاء طرق معاودة الاتصال بدورة الحياة
onCreate()وonStop()لتنفيذ عمليات في أوقات مختلفة في دورة حياة النشاط أو الجزء
ما ستتعرّف عليه
- كيفية إعداد أجزاء من تطبيقك وبدء تشغيلها وإيقافها في عمليات معاودة الاتصال بدورة الحياة
- كيفية استخدام مكتبة مراحل النشاط في Android لإنشاء مراقب مراحل النشاط وتسهيل إدارة مراحل نشاط الأنشطة واللقطات
- تأثير عمليات إيقاف تشغيل عمليات Android على البيانات في تطبيقك، وكيفية حفظ هذه البيانات واستعادتها تلقائيًا عندما يغلق Android تطبيقك
- كيف يؤدي تدوير الجهاز والتغييرات الأخرى في الإعدادات إلى تغيير حالات مراحل النشاط والتأثير في حالة تطبيقك
المهام التي ستنفذها
- عدِّل تطبيق DessertClicker لتضمين وظيفة موقّت، وابدأ هذا الموقّت وأوقِفه في أوقات مختلفة خلال مراحل نشاط التطبيق.
- عدِّل التطبيق لاستخدام مكتبة مراحل النشاط في Android، وحوِّل الفئة
DessertTimerإلى مراقب مراحل النشاط. - يمكنك إعداد واستخدام Android Debug Bridge (
adb) لمحاكاة إيقاف عملية تطبيقك بشكل إجباري وعمليات معاودة الاتصال بدورة الحياة التي تحدث بعد ذلك. - استخدِم طريقة
onSaveInstanceState()للاحتفاظ ببيانات التطبيق التي قد يتم فقدانها في حال إغلاق التطبيق بشكل غير متوقّع. أضِف رمزًا برمجيًا لاستعادة هذه البيانات عند إعادة تشغيل التطبيق.
في هذا الدرس التطبيقي حول الترميز، ستوسّع نطاق تطبيق DessertClicker من الدرس التطبيقي السابق حول الترميز. يمكنك إضافة مؤقّت في الخلفية، ثم تحويل التطبيق لاستخدام مكتبة مراحل النشاط في Android.

في الدرس العملي السابق، تعلّمت كيفية مراقبة دورات حياة الأنشطة واللقطات من خلال إلغاء العديد من عمليات معاودة الاتصال الخاصة بدورة الحياة، وتسجيل الوقت الذي يستدعي فيه النظام عمليات معاودة الاتصال هذه. في هذه المهمة، ستستكشف مثالاً أكثر تعقيدًا لإدارة مهام دورة الحياة في تطبيق DessertClicker. ستستخدم مؤقتًا يعرض عبارة سجلّ كل ثانية، مع عدد الثواني التي تم تشغيله فيها.
الخطوة 1: إعداد تطبيق DessertTimer
- افتح تطبيق DessertClicker من آخر تجربة عملية. (يمكنك تنزيل DessertClickerLogs من هنا إذا لم يكن التطبيق مثبّتًا لديك).
- في طريقة العرض المشروع، وسِّع java > com.example.android.dessertclicker وافتح
DessertTimer.kt. لاحظ أنّ جميع الرموز البرمجية معلّقة حاليًا، لذا لا يتم تنفيذها كجزء من التطبيق. - اختَر كل الرمز في نافذة المحرّر. اختَر الرمز > التعليق باستخدام تعليق السطر، أو اضغط على
Control+/(Command+/على جهاز Mac). يزيل هذا الأمر التعليقات من جميع الرموز في الملف. (قد يعرض Android Studio أخطاء مرجعية لم يتم حلّها إلى أن تعيد إنشاء التطبيق). - لاحظ أنّ الفئة
DessertTimerتتضمّنstartTimer()وstopTimer()، اللذين يبدآن المؤقّت ويوقفانه. عندما يكونstartTimer()قيد التشغيل، يطبع الموقّت رسالة سجلّ كل ثانية، مع إجمالي عدد الثواني التي تم تشغيل الموقّت خلالها. تؤدي الطريقةstopTimer()بدورها إلى إيقاف الموقّت وبيانات السجلّ.
- فتح "
MainActivity.kt" في أعلى الصف، أسفل المتغيرdessertsSoldمباشرةً، أضِف متغيرًا للمؤقت:
private lateinit var dessertTimer : DessertTimer;- انتقِل للأسفل إلى
onCreate()وأنشِئ عنصرDessertTimerجديدًا، بعد طلبsetOnClickListener()مباشرةً:
dessertTimer = DessertTimer()
بعد أن أصبح لديك عنصر موقّت للحلوى، فكِّر في المكان الذي يجب أن تبدأ فيه الموقّت وتوقفه لكي يعمل فقط عندما يكون النشاط على الشاشة. يمكنك الاطّلاع على بعض الخيارات في الخطوات التالية.
الخطوة 2: بدء الموقّت وإيقافه
يتم استدعاء الطريقة onStart() قبل أن يصبح النشاط مرئيًا مباشرةً. يتم استدعاء الطريقة onStop() بعد أن يتوقف النشاط عن الظهور. يبدو أنّ عمليات الاستدعاء هذه هي الخيار الأنسب لتحديد وقت بدء الموقّت وإيقافه.
- في الصف
MainActivity، ابدأ الموقّت في وظيفة معاودة الاتصالonStart():
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}- إيقاف الموقّت بعد
onStop():
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- جمِّع التطبيق وشغِّله. في "استوديو Android"، انقر على جزء أداة Logcat. في مربّع البحث Logcat، أدخِل
dessertclicker، ما يؤدي إلى الفلترة حسب الفئتينMainActivityوDessertTimer. يُرجى العِلم أنّه بمجرد بدء تشغيل التطبيق، سيبدأ المؤقّت أيضًا في العمل على الفور.
- انقر على الزر رجوع ولاحظ أنّ الموقّت يتوقّف مرة أخرى. يتوقف المؤقت لأنّه تم إيقاف كل من النشاط والمؤقت الذي يتحكّم فيه.
- استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. لاحظ في Logcat أنّ الموقّت يعيد التشغيل من 0.
- انقر على الزر مشاركة. لاحظ في Logcat أنّ الموقّت لا يزال يعمل.

- انقر على زر الشاشة الرئيسية. لاحظ في Logcat أنّ الموقّت يتوقف عن العمل.
- استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. لاحظ في Logcat أنّ الموقّت يبدأ من جديد من حيث توقّف.
- في
MainActivity، في طريقةonStop()، علِّق على طلبstopTimer(). يُظهر التعليق علىstopTimer()الحالة التي تبدأ فيها عملية فيonStart()، ولكنك تنسى إيقافها مرة أخرى فيonStop(). - جمِّع التطبيق وشغِّله، ثم انقر على زر "الصفحة الرئيسية" بعد بدء الموقّت. على الرغم من أنّ التطبيق يعمل في الخلفية، يظل الموقّت قيد التشغيل ويستخدم موارد النظام باستمرار. سيؤدي استمرار تشغيل الموقّت إلى حدوث تسرُّب للذاكرة في تطبيقك، ومن المحتمل ألا يكون هذا هو السلوك الذي تريده.
النمط العام هو أنّه عند إعداد شيء أو بدئه في دالة ردّ الاتصال، عليك إيقافه أو إزالته في دالة ردّ الاتصال المقابلة. بهذه الطريقة، تتجنّب تشغيل أي شيء عندما لا يعود هناك حاجة إليه.
- أزِل التعليق من السطر في
onStop()حيث توقف الموقّت. - قصّ المكالمة
startTimer()ولصقها منonStart()إلىonCreate()يوضّح هذا التغيير الحالة التي يتم فيها تهيئة مورد وبدء استخدامه فيonCreate()، بدلاً من استخدامonCreate()لتهيئته وonStart()لبدء استخدامه. - جمِّع التطبيق وشغِّله. ستلاحظ أنّ الموقّت يبدأ في العمل، كما هو متوقّع.
- انقر على "الصفحة الرئيسية" لإيقاف التطبيق، وسيتوقف المؤقت عن العمل كما هو متوقّع.
- استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. لاحظ أنّ المؤقت لا يبدأ من جديد في هذه الحالة، لأنّه يتم استدعاء
onCreate()فقط عند بدء تشغيل التطبيق، وليس عند رجوع التطبيق إلى المقدّمة.
في ما يلي النقاط الرئيسية التي يجب تذكُّرها:
- عند إعداد مورد في دالة ردّ الاتصال لدورة الحياة، يجب أيضًا إيقاف المورد.
- إجراء عمليات الإعداد والإزالة في الطرق المناسبة
- إذا أعددت شيئًا في
onStart()، يمكنك إيقافه أو إزالته مرة أخرى فيonStop().
في تطبيق DessertClicker، من السهل إلى حدّ ما معرفة أنّه إذا بدأت الموقّت في onStart()، عليك إيقافه في onStop(). لا يوجد سوى مؤقّت واحد، لذا لن يكون من الصعب تذكُّر إيقافه.
في تطبيق Android أكثر تعقيدًا، يمكنك إعداد العديد من العناصر في onStart() أو onCreate()، ثم إيقافها كلها في onStop() أو onDestroy(). على سبيل المثال، قد تحتاج إلى إعداد وإيقاف الصور المتحركة أو الموسيقى أو أجهزة الاستشعار أو المؤقتات، بالإضافة إلى بدءها وإيقافها. وإذا نسيت أحدها، سيؤدي ذلك إلى حدوث أخطاء ومشاكل.
تُسهّل مكتبة دورة الحياة، وهي جزء من Android Jetpack، هذه المهمة. تكون المكتبة مفيدة بشكل خاص في الحالات التي عليك فيها تتبُّع العديد من الأجزاء المتحركة، وبعضها في حالات مختلفة لدورة الحياة. تعكس المكتبة طريقة عمل دورات الحياة: عادةً ما يخبر النشاط أو الجزء أحد المكوّنات (مثل DessertTimer) بما يجب فعله عند حدوث معاودة الاتصال بدورة الحياة. ولكن عند استخدام مكتبة مراحل النشاط، يراقب المكوّن نفسه التغييرات في مراحل النشاط، ثم ينفّذ الإجراءات اللازمة عند حدوث هذه التغييرات.
تتضمّن مكتبة مراحل النشاط ثلاثة أجزاء رئيسية:
- المالكون لدورة الحياة، وهم المكوّنات التي لها دورة حياة (وبالتالي "تملكها").
ActivityوFragmentهما مالكا دورة الحياة. ينفّذ مالكو مراحل النشاط واجهةLifecycleOwner. - الفئة
Lifecycleالتي تحتوي على الحالة الفعلية لمالك دورة الحياة وتفعّل الأحداث عند حدوث تغييرات في دورة الحياة - مراقبون لدورة الحياة، يراقبون حالة دورة الحياة وينفّذون المهام عند تغيُّر دورة الحياة. تنفّذ عناصر مراقبة مراحل النشاط واجهة
LifecycleObserver.
في هذه المهمة، ستحوّل تطبيق DessertClicker لاستخدام مكتبة مراحل النشاط في Android، وستتعرّف على كيفية تسهيل المكتبة لإدارة مراحل النشاط في Android.
الخطوة 1: تحويل DessertTimer إلى LifecycleObserver
يُعدّ مفهوم مراقبة دورة الحياة جزءًا أساسيًا من مكتبة دورة الحياة. تتيح المراقبة للفئات (مثل DessertTimer) معرفة نشاط أو دورة حياة الجزء، وبدء وإيقاف نفسها استجابةً للتغييرات في حالات دورة الحياة هذه. باستخدام أداة مراقبة مراحل النشاط، يمكنك إزالة مسؤولية بدء الكائنات وإيقافها من طرق الأنشطة والتقسيمات.
- افتح صف
DesertTimer.kt. - غيِّر توقيع الفئة
DessertTimerليصبح على النحو التالي:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {يؤدي تعريف الفئة الجديد هذا وظيفتَين:
- تتلقّى الدالة الإنشائية عنصر
Lifecycle، وهو دورة الحياة التي يراقبها المؤقّت. - ينفّذ تعريف الفئة واجهة
LifecycleObserver.
- أسفل المتغيّر
runnable، أضِف كتلةinitإلى تعريف الفئة. في الحظرinit، استخدِم الطريقةaddObserver()لربط عنصر دورة الحياة الذي تم تمريره من المالك (النشاط) بهذه الفئة (المراقب).
init {
lifecycle.addObserver(this)
}- أضِف تعليقًا توضيحيًا إلى
startTimer()باستخدام@OnLifecycleEvent annotation، واستخدِم حدث مراحل النشاطON_START. تتوفّر جميع أحداث مراحل النشاط التي يمكن لمراقب مراحل النشاط مراقبتها في الفئةLifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {- كرِّر الخطوات نفسها مع
stopTimer()باستخدام الحدثON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()الخطوة 2: تعديل ملف MainActivity
فئة MainActivity هي مالك دورة حياة من خلال الوراثة، لأنّ الفئة الفائقة FragmentActivity تنفّذ LifecycleOwner. لذلك، ليس عليك اتّخاذ أي إجراء لجعل نشاطك متوافقًا مع مراحل النشاط. كل ما عليك فعله هو تمرير عنصر مراحل نشاط التطبيق إلى الدالة الإنشائية DessertTimer.
- فتح "
MainActivity" في طريقةonCreate()، عدِّل عملية تهيئةDessertTimerلتضمينthis.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)تحتوي السمة lifecycle الخاصة بالنشاط على العنصر Lifecycle الذي يملكه هذا النشاط.
- أزِل المكالمة إلى
startTimer()فيonCreate()، والمكالمة إلىstopTimer()فيonStop(). لم يعُد عليك إخبارDessertTimerبما يجب فعله في النشاط، لأنّDessertTimerيراقب الآن دورة الحياة نفسها ويتم إعلامه تلقائيًا عند تغيُّر حالة دورة الحياة. كل ما عليك فعله في عمليات معاودة الاتصال هذه هو تسجيل رسالة. - جمِّع التطبيق وشغِّله، ثم افتح Logcat. لاحظ أنّ الموقّت بدأ بالعمل كما هو متوقّع.

- انقر على زر الشاشة الرئيسية لوضع التطبيق في الخلفية. لاحظ أنّ الموقّت توقّف عن العمل، كما هو متوقّع.
ماذا يحدث لتطبيقك وبياناته إذا أوقف نظام التشغيل Android هذا التطبيق أثناء تشغيله في الخلفية؟ من المهم فهم هذه الحالة الحدّية المعقّدة.
عندما ينتقل تطبيقك إلى الخلفية، لا يتم إيقافه نهائيًا، بل يتم إيقافه مؤقتًا فقط وينتظر عودة المستخدم إليه. ولكن من أهم اهتمامات نظام التشغيل Android الحفاظ على سلاسة تشغيل النشاط الذي يظهر في المقدّمة. على سبيل المثال، إذا كان المستخدم يستعين بتطبيق لتحديد المواقع الجغرافية لمساعدته في اللحاق بحافلة، من المهم عرض هذا التطبيق بسرعة ومواصلة عرض الاتجاهات. من غير المهم إبقاء تطبيق DessertClicker، الذي قد لا يكون المستخدم قد اطّلع عليه لبضعة أيام، يعمل بسلاسة في الخلفية.
ينظّم Android التطبيقات التي تعمل في الخلفية حتى يتمكّن التطبيق الذي يعمل في المقدّمة من العمل بدون مشاكل. على سبيل المثال، يفرض نظام التشغيل Android قيودًا على مقدار المعالجة التي يمكن أن تجريها التطبيقات التي تعمل في الخلفية.
في بعض الأحيان، يوقف نظام التشغيل Android عملية تطبيق بأكملها، بما في ذلك كل نشاط مرتبط بالتطبيق. ويحدث هذا النوع من الإيقاف عندما يكون النظام تحت ضغط كبير ويواجه خطر التأخّر في العرض، لذا لا يتم تنفيذ أي عمليات ردّ اتصال أو رموز إضافية في هذه المرحلة. يتم ببساطة إيقاف عملية تطبيقك في الخلفية بدون تنبيه. ولكن بالنسبة إلى المستخدم، لا يبدو أنّه تم إغلاق التطبيق. عندما يعود المستخدم إلى تطبيق أوقف نظام التشغيل Android تشغيله، يعيد نظام التشغيل Android تشغيل هذا التطبيق.
في هذه المهمة، ستحاكي إيقاف عملية Android وفحص ما يحدث لتطبيقك عند إعادة تشغيله.
الخطوة 1: استخدام adb لمحاكاة إيقاف عملية
Android Debug Bridge (adb) هي أداة سطر أوامر تتيح لك إرسال تعليمات إلى المحاكيات والأجهزة المتصلة بجهاز الكمبيوتر. في هذه الخطوة، يمكنك استخدام adb لإغلاق عملية تطبيقك ومعرفة ما يحدث عندما يوقف نظام التشغيل Android تطبيقك.
- جمِّع تطبيقك وشغِّله، ثم انقر على رمز الكب كيك عدّة مرات.
- اضغط على زر "الشاشة الرئيسية" لوضع تطبيقك في الخلفية. تم إيقاف تطبيقك الآن، وسيتم إغلاقه إذا احتاج نظام التشغيل Android إلى الموارد التي يستخدمها التطبيق.
- في Android Studio، انقر على علامة التبويب Terminal لفتح نافذة سطر الأوامر.

- اكتب
adbواضغط على Return.
إذا ظهرت لك الكثير من النتائج التي تبدأ بـAndroid Debug Bridge version X.XX.Xوتنتهي بـtags to be used by logcat (see logcat —help)، يعني ذلك أنّ كل شيء على ما يرام. إذا ظهر لك الرمزadb: command not foundبدلاً من ذلك، تأكَّد من أنّ الأمرadbمتاح في مسار التنفيذ. للحصول على التعليمات، اطّلِع على القسم "إضافة adb إلى مسار التنفيذ" في فصل الأدوات المساعدة. - انسخ هذا التعليق والصقه في سطر الأوامر ثم اضغط على "رجوع":
adb shell am kill com.example.android.dessertclickerيطلب هذا الأمر من أي أجهزة أو محاكيات متصلة إيقاف العملية التي تحمل اسم الحزمة dessertclicker، ولكن فقط إذا كان التطبيق يعمل في الخلفية. بما أنّ تطبيقك كان يعمل في الخلفية، لا يظهر أي شيء على شاشة الجهاز أو المحاكي للإشارة إلى أنّه تم إيقاف عمليتك. في Android Studio، انقر على علامة التبويب تشغيل للاطّلاع على الرسالة "تم إنهاء التطبيق". انقر على علامة التبويب Logcat لترى أنّه لم يتم تشغيل معاودة الاتصال onDestroy() مطلقًا، بل انتهى نشاطك ببساطة.
- استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق، إذ يظهر تطبيقك في "التطبيقات الحديثة" سواء تم وضعه في الخلفية أو تم إيقافه تمامًا. عند استخدام شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق، يتم بدء النشاط مرة أخرى. يمر النشاط بمجموعة كاملة من عمليات معاودة الاتصال لدورة حياة بدء التشغيل، بما في ذلك
onCreate(). - لاحظ أنّه عند إعادة تشغيل التطبيق، تتم إعادة ضبط "النتيجة" (عدد الحلويات المباعة وإجمالي الدولارات) إلى القيم التلقائية (0). إذا أوقف نظام التشغيل Android تطبيقك، لماذا لم يتم حفظ حالته؟
عندما يعيد نظام التشغيل تشغيل تطبيقك، يحاول Android إعادة ضبط تطبيقك على الحالة التي كان عليها من قبل. يأخذ نظام التشغيل Android حالة بعض طرق العرض ويحفظها في حزمة كلما انتقلت بعيدًا عن النشاط. من الأمثلة على البيانات التي يتم حفظها تلقائيًا النص في عنصر EditText (طالما تم ضبط معرّف في التصميم)، وسجلّ التراجع عن النشاط.
ومع ذلك، في بعض الأحيان لا يعرف نظام التشغيل Android جميع بياناتك. على سبيل المثال، إذا كان لديك متغيّر مخصّص مثلrevenueفي تطبيق DessertClicker، لا يعرف نظام التشغيل Android هذه البيانات أو أهميتها لنشاطك. عليك إضافة هذه البيانات إلى الحزمة بنفسك.
الخطوة 2: استخدام onSaveInstanceState() لحفظ بيانات الحزمة
الطريقة onSaveInstanceState() هي ردّ الاتصال الذي تستخدمه لحفظ أي بيانات قد تحتاج إليها إذا أوقف نظام التشغيل Android تطبيقك. في مخطط ردّ الاتصال لدورة الحياة، يتم استدعاء onSaveInstanceState() بعد إيقاف النشاط. يتم استدعاؤها في كل مرة ينتقل فيها تطبيقك إلى الخلفية.

يمكنك اعتبار عملية استدعاء onSaveInstanceState() إجراءً أمنيًا، فهي تتيح لك فرصة حفظ كمية صغيرة من المعلومات في حزمة عند خروج نشاطك من المقدّمة. يحفظ النظام هذه البيانات الآن لأنّه إذا انتظر إلى أن يتم إيقاف تطبيقك، قد يكون نظام التشغيل تحت ضغط الموارد. يضمن حفظ البيانات في كل مرة توفُّر بيانات التحديث في الحزمة لاستعادتها عند الحاجة.
- في
MainActivity، يمكنك تجاهل عملية الاستدعاءonSaveInstanceState()وإضافة عبارة تسجيلTimber.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- جمِّع التطبيق وشغِّله، ثم انقر على زر الشاشة الرئيسية لوضعه في الخلفية. لاحظ أنّ عملية ردّ الاتصال
onSaveInstanceState()تحدث بعدonPause()وonStop():
مباشرةً. - في أعلى الملف، قبل تعريف الفئة مباشرةً، أضِف الثوابت التالية:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"ستستخدم هذه المفاتيح لحفظ البيانات واسترجاعها من حزمة حالة الجهاز.
- انتقِل للأسفل إلى
onSaveInstanceState()، ولاحظ المَعلمةoutStateالتي يكون نوعهاBundle.
الحزمة هي مجموعة من أزواج المفتاح/القيمة، وتكون المفاتيح دائمًا عبارة عن سلاسل. يمكنك وضع قيم أولية، مثل قيمintوboolean، في الحزمة.
بما أنّ النظام يحتفظ بهذه الحزمة في ذاكرة الوصول العشوائي، من أفضل الممارسات الحفاظ على صغر حجم البيانات في الحزمة. يتم أيضًا فرض حدّ أقصى لحجم هذه الحزمة، على الرغم من أنّ الحجم يختلف من جهاز إلى آخر. بشكل عام، يجب تخزين عدد أقل بكثير من 100 ألف، وإلا ستتعرّض لخطر تعطُّل تطبيقك بسبب الخطأTransactionTooLargeException. - في
onSaveInstanceState()، ضَع القيمةrevenue(عدد صحيح) في الحزمة باستخدام الطريقةputInt():
outState.putInt(KEY_REVENUE, revenue)تأخذ طريقة putInt() (والطرق المشابهة من فئة Bundle مثل putFloat() وputString() وسيطتَين: سلسلة للمفتاح (الثابت KEY_REVENUE) والقيمة الفعلية المطلوب حفظها.
- كرِّر العملية نفسها مع عدد الحلويات التي تم بيعها وحالة المؤقت:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)الخطوة 3: استخدام onCreate() لاستعادة بيانات الحزمة
- انتقِل للأعلى إلى
onCreate()، وافحص توقيع الطريقة:
override fun onCreate(savedInstanceState: Bundle) {لاحظ أنّ onCreate() تحصل على Bundle في كل مرة يتم استدعاؤها. عند إعادة تشغيل نشاطك بسبب إيقاف عملية ما، يتم تمرير الحزمة التي حفظتها إلى onCreate(). إذا كان نشاطك يبدأ من جديد، تكون هذه الحزمة في onCreate() هي null. لذلك، إذا لم تكن الحزمة null، ستعرف أنّك "تعيد إنشاء" النشاط من نقطة معروفة سابقًا.
- أضِف الرمز التالي إلى
onCreate()بعد إعدادDessertTimer:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}يحدّد الاختبار الخاص بـ null ما إذا كانت هناك بيانات في الحزمة، أو ما إذا كانت الحزمة null، وهو ما يوضّح لك بدوره ما إذا كان التطبيق قد بدأ من جديد أو تمت إعادة إنشائه بعد إيقافه. هذا الاختبار هو نمط شائع لاستعادة البيانات من الحزمة.
لاحظ أنّ المفتاح الذي استخدمته هنا (KEY_REVENUE) هو المفتاح نفسه الذي استخدمته في putInt(). للتأكّد من استخدام المفتاح نفسه في كل مرة، من أفضل الممارسات تحديد هذه المفاتيح كثوابت. يمكنك استخدام getInt() لاستخراج البيانات من الحزمة، تمامًا كما استخدمت putInt() لوضع البيانات في الحزمة. تأخذ الطريقة getInt() وسيطتَين:
- سلسلة تعمل كمفتاح، مثل
"key_revenue"لقيمة الإيرادات. - قيمة تلقائية في حال عدم توفّر قيمة لهذا المفتاح في الحزمة
يتم بعد ذلك تعيين العدد الصحيح الذي تحصل عليه من الحزمة إلى المتغيّر revenue، وستستخدِم واجهة المستخدم هذه القيمة.
- أضِف طُرق
getInt()لاستعادة عدد الحلويات التي تم بيعها وقيمة المؤقت:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}- جمِّع التطبيق وشغِّله. اضغط على الكب كيك خمس مرات على الأقل إلى أن يتحوّل إلى دونات. انقر على "الصفحة الرئيسية" لوضع التطبيق في الخلفية.
- في علامة التبويب Terminal في "استوديو Android"، شغِّل
adbلإيقاف عملية التطبيق.
adb shell am kill com.example.android.dessertclicker- استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. لاحظ أنّ التطبيق يعود هذه المرة مع قيم الأرباح والحلويات المباعة الصحيحة من الحزمة. لاحظ أيضًا أنّ الحلوى قد عادت إلى كعكة صغيرة. هناك خطوة أخرى يجب اتّخاذها لضمان عودة التطبيق من عملية الإغلاق بالطريقة نفسها التي تم إغلاقه بها.
- في
MainActivity، افحص طريقةshowCurrentDessert(). لاحظ أنّ هذه الطريقة تحدّد صورة الحلوى التي يجب عرضها في النشاط استنادًا إلى العدد الحالي من الحلويات المباعة وقائمة الحلويات في المتغيّرallDesserts.
for (dessert in allDesserts) {
if (dessertsSold >= dessert.startProductionAmount) {
newDessert = dessert
}
else break
}تعتمد هذه الطريقة على عدد الحلويات المَبيعة لاختيار الصورة المناسبة. لذلك، ليس عليك اتّخاذ أي إجراء لتخزين مرجع للصورة في الحزمة في onSaveInstanceState(). في هذه الحزمة، يتم تخزين عدد الحلويات التي تم بيعها.
- في
onCreate()، في القسم الذي يستعيد الحالة من الحزمة، استخدِم الدالةshowCurrentDessert():
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}- جمِّع التطبيق وشغِّله، ثم ضعه في الخلفية. استخدِم
adbلإيقاف العملية. استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. لاحظ الآن أنّه تم استعادة كلّ من قيم الحلويات التي تم إدخالها وإجمالي الأرباح وصورة الحلوى بشكلٍ صحيح.
هناك حالة خاصة أخيرة في إدارة دورة حياة النشاط والجزء من المهم فهمها، وهي: كيف تؤثر تغييرات الإعدادات في دورة حياة الأنشطة والأجزاء.
يحدث تغيير في الإعدادات عندما تتغير حالة الجهاز بشكل جذري، ويكون أسهل طريقة يحلّ بها النظام هذا التغيير هي إيقاف النشاط تمامًا وإعادة إنشائه. على سبيل المثال، إذا غيّر المستخدم لغة الجهاز، قد يحتاج التنسيق بأكمله إلى التغيير لاستيعاب اتجاهات النص المختلفة. إذا وصل المستخدم الجهاز بقاعدة أو أضاف لوحة مفاتيح خارجية، قد يحتاج تصميم التطبيق إلى الاستفادة من حجم أو تصميم عرض مختلف. وإذا تغيّر اتجاه الجهاز، أي إذا تم تدويره من الوضع العمودي إلى الوضع الأفقي أو العكس، قد تحتاج إلى تغيير التصميم ليتناسب مع الاتجاه الجديد.
الخطوة 1: استكشاف تدوير الجهاز وعمليات معاودة الاتصال بدورة الحياة
- جمِّع تطبيقك وشغِّله، ثم افتح Logcat.
- أدِر الجهاز أو المحاكي لاستخدام الوضع الأفقي. يمكنك تدوير المحاكي إلى اليسار أو اليمين باستخدام أزرار التدوير أو مفتاحَي
Controlوالسهمين (Commandومفتاحَي السهمين على جهاز Mac).
- افحص الناتج في Logcat. فلترة النتائج حسب
MainActivity
يُرجى العِلم أنّه عندما يدير الجهاز أو المحاكي الشاشة، يستدعي النظام جميع عمليات معاودة الاتصال بدورة الحياة لإيقاف النشاط. بعد ذلك، أثناء إعادة إنشاء النشاط، يستدعي النظام جميع عمليات معاودة الاتصال بدورة الحياة لبدء النشاط. - في
MainActivity، علِّق على طريقةonSaveInstanceState()بأكملها. - أعِد تجميع تطبيقك وتشغيله. انقر على الكعكة الصغيرة عدة مرات، ثم أدرِ الجهاز أو المحاكي. في هذه المرة، عند تدوير الجهاز وإيقاف النشاط وإعادة إنشائه، يبدأ النشاط بالقيم التلقائية.
عند حدوث تغيير في الإعدادات، يستخدم نظام التشغيل Android حزمة حالة المثيل نفسها التي تعرّفت عليها في المهمة السابقة لحفظ حالة التطبيق واستعادتها. وكما هو الحال مع إيقاف العملية، استخدِمonSaveInstanceState()لوضع بيانات تطبيقك في الحزمة. بعد ذلك، استعِد البيانات فيonCreate()لتجنُّب فقدان بيانات حالة النشاط في حال تدوير الجهاز. - في
MainActivity، أزِل التعليق من طريقةonSaveInstanceState()، وشغِّل التطبيق، وانقر على الكعكة الصغيرة، ثم أدر التطبيق أو الجهاز. لاحظ أنّ بيانات الحلوى يتم الاحتفاظ بها هذه المرة عند تكرار النشاط.
مشروع "استوديو Android": DessertClickerFinal
نصائح حول دورة الحياة
- إذا أعددت أو بدأت إجراءً في دالة ردّ الاتصال لدورة الحياة، أوقِف هذا الإجراء أو أزِله في دالة ردّ الاتصال المقابلة. من خلال إيقاف التطبيق أو العملية، يمكنك التأكّد من عدم استمرار تشغيلهما عندما لا تكون هناك حاجة إليهما. على سبيل المثال، إذا ضبطت موقّتًا في
onStart()، عليك إيقافه مؤقتًا أو إيقافه فيonStop(). - استخدِم
onCreate()فقط لتهيئة أجزاء التطبيق التي يتم تشغيلها مرة واحدة عند بدء تشغيل التطبيق لأول مرة. استخدِمonStart()لبدء أجزاء تطبيقك التي يتم تشغيلها عند بدء تشغيل التطبيق وفي كل مرة يعود فيها إلى المقدّمة.
مكتبة Lifecycle
- استخدِم مكتبة دورة حياة Android لنقل التحكّم في دورة الحياة من النشاط أو الجزء إلى المكوّن الفعلي الذي يحتاج إلى أن يكون على دراية بدورة الحياة.
- المالكون في دورة الحياة هم مكوّنات لها دورات حياة (وبالتالي "تملكها")، بما في ذلك
ActivityوFragment. ينفّذ مالكو مراحل النشاط واجهةLifecycleOwner. - تراقب الكائنات المراقِبة دورة الحياة الحالية وتنفّذ المهام عند تغيُّر دورة الحياة. تنفّذ عناصر مراقبة مراحل النشاط واجهة
LifecycleObserver. - تحتوي عناصر
Lifecycleعلى حالات دورة الحياة الفعلية، وتؤدي إلى تشغيل الأحداث عند تغيُّر دورة الحياة.
لإنشاء فئة تتوافق مع مراحل النشاط، اتّبِع الخطوات التالية:
- نفِّذ واجهة
LifecycleObserverفي الفئات التي تحتاج إلى أن تكون على دراية بدورة الحياة. - يمكنك تهيئة فئة مراقبة دورة الحياة باستخدام عنصر دورة الحياة من النشاط أو الجزء.
- في فئة مراقب مراحل النشاط، أضِف تعليقًا توضيحيًا إلى الطرق التي تتوافق مع مراحل النشاط مع تغيير حالة مراحل النشاط التي تهمّها.
على سبيل المثال، يشير التعليق التوضيحي@OnLifecycleEvent(Lifecycle.Event.ON_START)إلى أنّ الطريقة تراقب حدث دورة الحياةonStart.
إيقاف العمليات وحفظ حالة النشاط
- ينظّم نظام التشغيل Android التطبيقات التي تعمل في الخلفية حتى يتمكّن التطبيق الذي يظهر على الشاشة من العمل بدون مشاكل. يتضمّن هذا القانون الحدّ من مقدار المعالجة التي يمكن أن تجريها التطبيقات في الخلفية، وفي بعض الأحيان إيقاف عملية التطبيق بأكملها.
- لا يمكن للمستخدم معرفة ما إذا كان النظام قد أوقف تطبيقًا في الخلفية. سيظل التطبيق يظهر في شاشة "التطبيقات الحديثة" ويجب إعادة تشغيله بالحالة نفسها التي تركه المستخدم عليها.
- Android Debug Bridge (
adb) هي أداة سطر أوامر تتيح لك إرسال تعليمات إلى المحاكيات والأجهزة المتصلة بجهاز الكمبيوتر. يمكنك استخدامadbلمحاكاة إيقاف عملية في تطبيقك. - عندما يوقف نظام التشغيل Android عملية تطبيقك، لا يتم استدعاء طريقة دورة الحياة
onDestroy(). يتوقف التطبيق فجأة.
الحفاظ على حالة النشاط والجزء
- عندما ينتقل تطبيقك إلى الخلفية، يتم حفظ بيانات التطبيق في حزمة بعد استدعاء
onStop()مباشرةً. يتم تلقائيًا حفظ بعض بيانات التطبيقات، مثل محتوىEditText. - الحزمة هي مثيل من
Bundle، وهي مجموعة من المفاتيح والقيم. تكون المفاتيح دائمًا سلاسل. - استخدِم الدالة
onSaveInstanceState()للحفظ في الحزمة أي بيانات أخرى تريد الاحتفاظ بها، حتى إذا تم إغلاق التطبيق تلقائيًا. لوضع البيانات في الحزمة، استخدِم طرق الحزمة التي تبدأ بـput، مثلputInt(). - يمكنك استرداد البيانات من الحزمة باستخدام الطريقة
onRestoreInstanceState()، أو بشكل أكثر شيوعًا فيonCreate(). تحتوي الطريقةonCreate()على المَعلمةsavedInstanceStateالتي تحتوي على الحزمة. - إذا كان المتغيّر
savedInstanceStateيحتوي علىnull، يعني ذلك أنّه تم بدء النشاط بدون حزمة حالة، ولا تتوفّر بيانات حالة لاستردادها. - لاسترداد البيانات من الحزمة باستخدام مفتاح، استخدِم طرق
Bundleالتي تبدأ بـget، مثلgetInt().
تغييرات الإعداد
- يحدث تغيير في الإعدادات عندما تتغيّر حالة الجهاز بشكل جذري، بحيث تكون أسهل طريقة يحلّ بها النظام هذا التغيير هي إيقاف النشاط وإعادة إنشائه.
- أكثر الأمثلة شيوعًا على تغيير الإعدادات هو عندما يدوّر المستخدم الجهاز من الوضع العمودي إلى الوضع الأفقي أو العكس. يمكن أن يحدث تغيير في الإعدادات أيضًا عند تغيير لغة الجهاز أو توصيل لوحة مفاتيح خارجية.
- عند حدوث تغيير في الإعدادات، يستدعي نظام التشغيل Android جميع عمليات معاودة الاتصال بإيقاف نشاط التطبيق. بعد ذلك، يعيد نظام التشغيل Android تشغيل النشاط من البداية، ويشغّل جميع عمليات معاودة الاتصال الخاصة ببدء دورة الحياة.
- عندما يغلق نظام التشغيل Android تطبيقًا بسبب تغيير في الإعدادات، يعيد تشغيل النشاط باستخدام حِزمة الحالة المتاحة لـ
onCreate(). - كما هو الحال مع إيقاف العملية، احفظ حالة تطبيقك في الحزمة في
onSaveInstanceState().
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
- الأنشطة (دليل واجهة برمجة التطبيقات)
Activity(مرجع واجهة برمجة التطبيقات)- فهم مراحل نشاط التطبيق
- التعامل مع دورات الحياة باستخدام المكوّنات التي تراعي دورة الحياة
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- التعامل مع تغييرات الإعدادات
- حفظ حالات واجهة المستخدم
غير ذلك:
- Timber (GitHub)
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
تغيير تطبيق
افتح تطبيق DiceRoller من الدرس 1. (يمكنك تنزيل التطبيق من هنا إذا لم يكن مثبّتًا لديك). جمِّع التطبيق وشغِّله، ولاحِظ أنّه في حال تدوير الجهاز، ستفقد القيمة الحالية للنرد. استخدِم onSaveInstanceState() للاحتفاظ بهذه القيمة في الحزمة، واستعادة هذه القيمة في onCreate().
الإجابة عن هذه الأسئلة
السؤال 1
يتضمّن تطبيقك محاكاة للفيزياء تتطلّب عمليات حسابية معقّدة لعرضها. بعد ذلك، يتلقّى المستخدم مكالمة هاتفية. أيّ عبارة من العبارات التالية تُعتبر صحيحة؟
- أثناء المكالمة الهاتفية، يجب مواصلة احتساب مواضع العناصر في محاكاة الفيزياء.
- أثناء المكالمة الهاتفية، يجب إيقاف احتساب مواضع العناصر في محاكاة الفيزياء.
السؤال 2
ما هي طريقة مراحل النشاط التي يجب إلغاؤها مؤقتًا لإيقاف المحاكاة مؤقتًا عندما لا يكون التطبيق معروضًا على الشاشة؟
onDestroy()onStop()onPause()onSaveInstanceState()
السؤال 3
لجعل إحدى الفئات تتوافق مع دورة الحياة من خلال مكتبة دورة حياة Android، أي واجهة يجب أن تنفّذها الفئة؟
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
السؤال 4
في أي من الحالات التالية تتلقّى الدالة onCreate() في نشاطك Bundle يتضمّن بيانات (أي أنّ Bundle ليس null)؟ قد ينطبق أكثر من جواب واحد.
- تتم إعادة تشغيل النشاط بعد تدوير الجهاز.
- يبدأ النشاط من البداية.
- يتم استئناف النشاط بعد الرجوع من الخلفية.
- تمت إعادة تشغيل الجهاز.
ابدأ الدرس التالي:
للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.