أساسيات أندرويد بجافا

الأنشطة ودورة حياة النشاط

18 دقيقة الدرس 4 من 12

الأنشطة ودورة حياة النشاط

Activity هي اللبنة الأساسية لواجهة مستخدم Android. كل شاشة يراها المستخدم — نموذج تسجيل دخول، خريطة، صفحة إعدادات — تُعتمد في الغالب على Activity. لكن على عكس نافذة سطح المكتب التي تظل مفتوحة حتى يغلقها المستخدم، تخضع Activity على Android لتصرف نظام التشغيل، الذي يمكنه إيقافها مؤقتًا أو إيقافها كليًا أو حتى تدميرها وإعادة إنشائها في أي وقت ولأسباب متعددة: مكالمة هاتفية واردة، أو تدوير الشاشة من قِبَل المستخدم، أو حاجة النظام إلى ذاكرة وصول عشوائي. دورة حياة النشاط (Activity Lifecycle) هي العقد الرسمي الذي يحدد متى تحدث كل من هذه التحولات وما الذي يُفترض بك فعله استجابةً لها.

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

ردود النداء الستة الأساسية لدورة الحياة

يستدعي Android مجموعة من الأساليب النموذجية على فئة Activity الفرعية الخاصة بك عند انتقالها عبر حالاتها المختلفة. عليك تجاوز (override) تلك التي تحتاجها. الستة التي يجب معرفتها هي:

  • onCreate(Bundle savedInstanceState) — يُستدعى مرة واحدة عند إنشاء النشاط للمرة الأولى (أو إعادة إنشائه بعد قتله). هنا تقوم بنفخ التخطيط وربط العروض واستعادة الحالة المحفوظة وأي إعداد يحدث مرة واحدة. استدع super.onCreate() دائمًا أولًا.
  • onStart() — النشاط على وشك أن يصبح مرئيًا. يُوضع هنا التهيئة الخفيفة التي تحدث في كل مرة تصبح الشاشة مرئية.
  • onResume() — النشاط في المقدمة ويتلقى إدخال المستخدم. تُشغَّل هنا الحركات والتدفقات من الكاميرا والمستشعرات وكل ما يستهلك الطاقة.
  • onPause() — نشاط آخر على وشك أخذ التركيز (مثل نافذة حوار أو مكالمة واردة). هذا آخر رد نداء مضمون إذا قتل النظام عمليتك. احفظ هنا البيانات الحرجة غير المحفوظة. اجعله سريعًا — لا يمكن للنشاط الجديد أن يبدأ قبل أن يُكمل onPause() تنفيذه.
  • onStop() — النشاط لم يعد مرئيًا. أطلق الموارد الأثقل (مؤشرات قاعدة البيانات، ومستقبلات البث، واتصالات الشبكة). ملاحظة: في API 28 وما فوق، يمكن للنظام إنهاء العملية دون استدعاء onDestroy() بعد هذه الحالة.
  • onDestroy() — يُطوى النشاط إما لأن المستخدم أنهاه أو لأن النظام يحتاج إلى ذاكرة. أطلق أي موارد متبقية. لا تعتمد على استدعاء هذا الأسلوب.

ثمة أيضًا ردا نداء للعودة:

  • onRestart() — يُستدعى بعد onStop() عندما يعود المستخدم إلى النشاط (قبل onStart()). نادر الاستخدام لكنه مفيد لإعادة تعيين الحالة المؤقتة.
  • onSaveInstanceState(Bundle outState) — يُستدعى قبل أن يُدمَّر النشاط لتتمكن من حفظ حالة واجهة المستخدم الخفيفة (موضع التمرير، محتوى حقول النص). تُمرَّر الحزمة إلى onCreate() عند إعادة إنشاء النشاط.

آلة حالات دورة الحياة

فكّر في النشاط وكأنه يتنقل عبر ثلاث مناطق متداخلة:

  • العمر الكامل: onCreate → … → onDestroy
  • العمر المرئي: onStart → … → onStop
  • عمر المقدمة: onResume → … → onPause
الزوج الأهم هو onResume / onPause. كل ما يجب أن يعمل فقط بينما النشاط يحظى باهتمام المستخدم الكامل يُحصل عليه في onResume ويُحرَّر في onPause. إذا حصلت على مورد في onCreate ونسيت تحريره في onPause، ستحتفظ به حتى حين يكون نشاط آخر في المقدمة.

هيكل عظمي عملي لنشاط

package com.example.lifecycle; import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; // يُستدعى مرة واحدة عند إنشاء النشاط (أو إعادة إنشائه بعد القتل) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // دائمًا أولًا setContentView(R.layout.activity_main); // نفخ التخطيط if (savedInstanceState != null) { // استعادة الحالة المحفوظة في onSaveInstanceState() String restoredText = savedInstanceState.getString("user_input", ""); Log.d(TAG, "Restored: " + restoredText); } Log.d(TAG, "onCreate"); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart -- النشاط مرئي"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume -- النشاط في المقدمة، شغّل المستشعرات / الحركة"); // مثال: startCamera(); startLocationUpdates(); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause -- على وشك فقدان التركيز، احفظ البيانات الحرجة"); // مثال: saveUserProgress(); stopCamera(); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop -- لم يعد مرئيًا، أطلق الموارد الثقيلة"); // مثال: unregisterReceiver(); closeDatabase(); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); // تنظيف أخير — لا يُضمن استدعاؤه! } // احفظ حالة واجهة المستخدم الخفيفة قبل أن يُقتل النشاط @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("user_input", "نص كتبه المستخدم"); Log.d(TAG, "onSaveInstanceState"); } }
استدع دائمًا النسخة super من كل رد نداء لدورة الحياة. يُضمّن الإطار منطقه الخاص (مخازن ViewModel، مديرو الأجزاء، WindowDecor) داخل استدعاءات super. تخطّيها يُسبب أعطالًا خفية يصعب تتبعها — وفي بعض الحالات استثناءً فوريًا.

مشكلة التدوير — لماذا تهم إعادة الإنشاء

تدوير الشاشة هو السيناريو الذي يُعثر معظم المبتدئين في Android. عند تدوير المستخدم للجهاز، يُدمّر Android نشاطك ويُعيد إنشاءه افتراضيًا. تسلسل الاستدعاءات هو:

  1. onPause()
  2. onStop()
  3. onSaveInstanceState() (فرصتك لحفظ حالة واجهة المستخدم)
  4. onDestroy()
  5. onCreate(savedInstanceState) — مع الحزمة التي ملأتها في الخطوة 3
  6. onStart()
  7. onResume()

هذا يعني أن أي بيانات خزّنتها في حقل على كائن النشاط اختفت بعد التدوير ما لم تحفظها. تتعامل آلية Bundle مع القيم الصغيرة القابلة للتسلسل. للكائنات الأكبر (استجابات الشبكة، ViewModels)، استخدم ViewModel من مكتبة AndroidX Lifecycle — فـ ViewModel يصمد أمام التدوير ويُغطى بعمق في درس إدارة البيانات.

لا تضع android:configChanges لإيقاف معالجة التدوير. تقترح كثير من الدروس التعليمية إضافة android:configChanges="orientation|screenSize" في الملف التعريفي لمنع إعادة الإنشاء. هذا يتجاوز النظام ويجعل تخطيطك قديمًا عند أحداث الطيّ/النشر، والانتقال إلى الوضع متعدد النوافذ، وتغييرات حجم الخط. تعلّم كيفية التعامل مع إعادة الإنشاء بشكل صحيح بدلًا من ذلك.

قواعد عملية لكل رد نداء

  • onCreate: انفخ التخطيط، اربط العروض، استعد حالة الحزمة، أعدّ المستمعين لمرة واحدة. لا تبدأ عملًا مستمرًا هنا.
  • onResume / onPause: الكاميرا، GPS، المستشعرات، تشغيل الوسائط، كل ما يستنزف البطارية أو يجب أن يكون حصريًا للتطبيق في المقدمة.
  • onStart / onStop: مستقبلات البث المرتبطة بواجهة المستخدم المرئية، والمؤقتات التي تحدّث العرض.
  • onDestroy: تنظيف الملاذ الأخير فقط؛ تعامل معه باعتباره غير موثوق. استخدم onStop() لأي شيء مهم.
  • onSaveInstanceState: القيم الأولية وكائنات Parcelable فقط — اجعله صغيرًا. لأي شيء أكبر، استخدم ViewModel.

تسجيل أحداث دورة الحياة

أثناء بناء أي نشاط جديد، أضف استدعاءات Log.d(TAG, "onXxx") لكل رد نداء وراقب Logcat في Android Studio. رؤية التسلسل الفعلي أثناء تدوير الجهاز وتمييع التطبيق والعودة إليه يساوي أكثر من أي مخطط. صفّي Logcat بـ TAG الخاص بك وستصبح الانتقالات شفافة تمامًا.

الخلاصة

يعيش كل نشاط Android داخل دورة حياة رسمية يديرها نظام التشغيل. تشكّل ردود النداء الستة — onCreate وonStart وonResume وonPause وonStop وonDestroy — آلة حالات تخبرك تمامًا متى تكون شاشتك مرئية أو في التركيز أو مختفية. اقرن اقتناء المورد بتحريره: شغّل الكاميرا في onResume وأوقفها في onPause؛ سجّل المستقبل في onStart وألغِ تسجيله في onStop. احفظ حالة واجهة المستخدم الخفيفة في onSaveInstanceState واستعدها في onCreate. احترم هذا العقد وسيتصرف نشاطك بشكل صحيح بغض النظر عن التدوير والمكالمات الواردة أو حالات انخفاض الذاكرة.