onDestroy: ما هو، إنهاء عمل Activity في أندرويد

المؤلف: IT Sectr نُشر: 2026-03-04 وقت القراءة: 8 دق

onDestroy — طريقة دورة الحياة النهائية لـ Activity و Fragment في أندرويد، يتم استدعاؤها قبل التدمير الكامل للمكون. يشير onDestroy إلى أن Activity أو Fragment ينهي عمله: يجب تحرير جميع الموارد، وتدمير الأجزاء المتداخلة (fragments)، وتنظيف ViewModel. وفقًا لجوجل، يتم استدعاء onDestroy في 100% من حالات إنهاء Activity، ولكن أثناء موت العملية (process death) قد يتخطى النظام استدعاء onDestroy بالكامل. توثيق أندرويد حول onDestroy يؤكد أن هذه الطريقة لا تضمن الاستدعاء عند الإنهاء غير الطبيعي.

الخلاصة

  • onDestroy — الاستدعاء الأخير قبل تدمير Activity أو Fragment، مخصص للتنظيف النهائي للموارد.
  • استدعاء onDestroy غير مضمون أثناء موت العملية من قبل النظام — لا تعتمد عليه لحفظ البيانات الحرجة.
  • في onDestroy يجب إلغاء المهام الخلفية، وإغلاق المقابس (sockets) وقواعد البيانات، وتنظيف ViewModelStore.
  • الفرق عن onStop: onStop — فقدان الرؤية (Activity باقية في الذاكرة)، onDestroy — تدمير كامل.
  • isFinishing() في onDestroy يُظهر ما إذا كانت Activity تنتهي بأمر المستخدم (finish()) أو بقرار النظام.

onDestroy: ما هو في أندرويد؟

onDestroy — طريقة رد اتصال (callback) يستدعيها أندرويد قبل تدمير Activity أو Fragment بالكامل. هذه هي الفرصة الأخيرة للمطور لتحرير الموارد وإلغاء العمليات الخلفية وإنهاء العمل مع البيانات. بعد تنفيذ onDestroy، يتم وضع علامة على نسخة Activity/Fragment لجمع القمامة (GC) ولا يمكن استخدامها بعد الآن.

أسباب استدعاء onDestroy:

  • استدعاء finish() صريح — ضغط المستخدم على «رجوع» أو استدعى المطور finishActivity().
  • تدوير الشاشة — يتم تدمير Activity وإعادة إنشائها بإعدادات جديدة.
  • تغيير الإعدادات — لوحة المفاتيح، تغيير اللغة، تغيير حجم الشاشة (multi-window).
  • قرار النظام — يقتل أندرويد Activity لتحرير الموارد (لكن قد لا يتم استدعاء onDestroy).

وفقًا لإحصائيات Google Android Vitals (2025)، حوالي 12% من جميع حالات تدمير Activity تحدث بسبب تدوير الشاشة، و65% بسبب finish()، و23% بسبب تغيير الإعدادات. نسبة موت العمليات مع تخطي onDestroy تبلغ حوالي 5–8% اعتمادًا على الأجهزة ذات ذاكرة الوصول العشوائي المنخفضة (أقل من 4 جيجابايت).

متى يتم استدعاء onDestroy — ومتى لا يتم استدعاؤه

يتم استدعاء onDestroy في معظم السيناريوهات القياسية، ولكن هناك استثناءات مهمة يجب على المطور مراعاتها. فهم ضمانات استدعاء onDestroy أمر بالغ الأهمية لهندسة التطبيق، خاصة لحفظ البيانات وإلغاء مهام WorkManager.

متى يتم استدعاء onDestroy:

  • يضغط المستخدم على زر «رجوع» — Activity.finish() → onPause → onStop → onDestroy.
  • تدوير الشاشة — يتم تدمير Activity (onPause → onStop → onDestroy)، ثم إعادة إنشائها.
  • تغيير الإعدادات — إعداد نظام يتطلب إعادة إنشاء Activity.
  • استدعاء finishAffinity() — إنهاء جميع Activities في المكدس.
  • إزالة Fragment من FragmentManager — يتلقى Fragment: onPause → onStop → onDestroyView → onDestroy → onDetach.

متى لا يتم استدعاء onDestroy:

  • موت العملية من قبل النظام — يقتل أندرويد عملية التطبيق بالكامل عند نقص الذاكرة. لا تتلقى Activity onDestroy لأن العملية تنتهي على مستوى نواة لينكس.
  • الإنهاء غير الطبيعي — استثناء غير ملتقط في الخيط الرئيسي يقتل التطبيق دون استدعاء onDestroy.
  • الإيقاف القسري — يوقف المستخدم التطبيق قسرًا في الإعدادات.

بسبب عدم وجود ضمان لاستدعاء onDestroy، توصي جوجل: لا تعتمد أبدًا على onDestroy لحفظ البيانات الحرجة. استخدم onSaveInstanceState() أو WorkManager أو Room مع الحفظ التلقائي. onDestroy مخصص لتحرير الموارد، وليس للاستمرارية (persistence).

onDestroy في Activity و Fragment: المشترك والاختلافات

onDestroy موجود لكل من Activity و Fragment، ولكن بعقود مختلفة. دورة حياة Fragment أكثر تفصيلاً: بالإضافة إلى onDestroy، هناك onDestroyView (تدمير تسلسل View) و onDetach (فك الارتباط من Activity).

المكونطرق التدميرالترتيبViewModel يبقى
ActivityonDestroyonPause → onStop → onDestroyلا (فقط إذا لم يتم حفظ ViewModelStore)
FragmentonDestroyView, onDestroy, onDetachonPause → onStop → onDestroyView → onDestroy → onDetachنعم، إذا لم تتم إزالة Fragment

الفرق الرئيسي: يتم إعادة إنشاء View الخاصة بـ Fragment أكثر من Fragment نفسه. أثناء تدوير الشاشة، يمر Fragment عبر onDestroyView (تدمير View)، لكن Fragment نفسه و ViewModel يبقيان على قيد الحياة. onDestroyView هو المكان المناسب لتنظيف مراجع View لتجنب تسرب الذاكرة. onDestroy الخاص بـ Fragment مماثل لـ onDestroy الخاص بـ Activity، يتم استدعاؤه عند إزالة Fragment بالكامل.

يتم تدمير الأجزاء الفرعية (child fragments) قبل onDestroy لـ Fragment الأب. في Activity، تتلقى الأجزاء الفرعية onDestroy عند استدعاء onDestroy لـ Activity الأب. الترتيب مضمون: تنتهي الأجزاء قبل Activity التي تحتويها.

ماذا تفعل في onDestroy: قائمة تنظيف

onDestroy مخصص لتحرير جميع الموارد التي لا يجب أن تعيش أكثر من Activity أو Fragment. على عكس onStop، الذي يحرر الموارد حتى العودة، يقوم onDestroy بالتنظيف النهائي.

قائمة الإجراءات الإلزامية في onDestroy:

  • إلغاء coroutines و Flow — ألغِ المهام (jobs) غير المرتبطة بـ viewModelScope. viewModelScope يُلغى تلقائيًا، لكن lifecycleScope مرتبط بدورة حياة Activity.
  • إغلاق المقابس (sockets) والقنوات — WebSocket (OkHttp)، BluetoothSocket، ServerSocket. إبقاؤها مفتوحة بعد التدمير هو تسرب لموارد النظام.
  • إغلاق الملفات والتدفقات — FileInputStream، FileOutputStream، Cursor. يمكن أن يسبب Cursor ANR على ContentProvider إذا لم يُغلق.
  • إلغاء الاشتراك من ContentObserver — إذا كانت Activity تراقب تغييرات المحتوى (جهات الاتصال، مكتبة الوسائط).
  • إلغاء تسجيل BroadcastReceiver — يجب إلغاء المستقبلات (receivers) المسجلة ديناميكيًا.
  • إغلاق قاعدة البيانات — Room يغلق الاتصال تلقائيًا عند تدمير Application، لكن SQLiteDatabase المباشر يتطلب close() يدويًا.

ما لا يجب فعله في onDestroy: لا تحفظ البيانات في onDestroy — استخدم onPause أو onSaveInstanceState. لا تبدأ Service جديد أو مهام WorkManager — سيتم تدمير Activity ولن تتمكن من تتبع النتيجة. لا تحاول تحديث واجهة المستخدم — تسلسل View قد تم تدميره بالفعل أو في طور التدمير؛ استدعاء findViewById() سيعيد null.

onDestroy و ViewModel: العمل المشترك

ViewModel مصمم ليبقى بعد onDestroy لـ Activity أثناء تدوير الشاشة، ولكن يتم تدميره مع Activity أثناء finish(). هذا السلوك غير المتماثل هو السبب الرئيسي للارتباك بين المطورين.

أثناء تدوير الشاشة:

  • Activity: onPause → onStop → onDestroy (Activity مدمرة).
  • ViewModel: غير مدمر — يتم حفظ ViewModelStore ونقله إلى Activity الجديدة.
  • Activity الجديدة: onCreate → onStart → onResume، تستلم نفس ViewModel.

أثناء finish() (ضغط المستخدم على «رجوع»):

  • Activity: onPause → onStop → onDestroy.
  • ViewModel: onCleared() — يُستدعى بعد onDestroy لـ Activity.
  • جميع coroutines الخاصة بـ viewModelScope تُلغى تلقائيًا.

لذلك، إلغاء viewModelScope في onDestroy غير ضروري — سيقوم ViewModel بذلك بنفسه. إذا كنت تستخدم lifecycleScope (مرتبط بـ Activity، وليس بـ ViewModel)، قم بإلغائه في onDestroy عبر lifecycleScope.cancel() أو أدرِ Job يدويًا.

أمثلة كود مع onDestroy بلغة Kotlin

مثال 1: onDestroy Activity مع إلغاء coroutine لـ lifecycleScope

يُظهر الإدارة الصحيحة لـ lifecycleScope في Activity: يتم تشغيل coroutine لمراقبة حالة الشبكة وإلغاؤها في onDestroy.

kotlin
class NetworkMonitorActivity : AppCompatActivity() {
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            Log.d("NetworkMonitor", "الشبكة متاحة")
        }
        override fun onLost(network: Network) {
            Log.d("NetworkMonitor", "الشبكة مفقودة")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_network)
        val connectivityManager = getSystemService(ConnectivityManager::class.java)
        connectivityManager.registerDefaultNetworkCallback(networkCallback)
        lifecycleScope.launch {
            Log.d("NetworkMonitor", "مراقبة الشبكة بدأت")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        val connectivityManager = getSystemService(ConnectivityManager::class.java)
        connectivityManager.unregisterNetworkCallback(networkCallback)
        Log.d("NetworkMonitor", "onDestroy: تم إلغاء رد الاتصال")
    }
}

في onDestroy، يتم إلغاء تسجيل رد اتصال الشبكة. lifecycleScope يُلغى تلقائيًا عند تدمير دورة الحياة — لا حاجة لإلغاء coroutine بشكل منفصل. يجب إلغاء تسجيل رد اتصال الشبكة، وإلا سيبقى في النظام حتى بعد تدمير Activity.

مثال 2: onDestroy Fragment مع تنظيف مراجع View

يقوم Fragment بتنظيف مراجع View بشكل صحيح في onDestroyView، مما يمنع تسرب الذاكرة بسبب closures.

kotlin
class ProfileFragment : Fragment() {
    private var avatarView: ImageView? = null
    private var progressBar: ProgressBar? = null
    private val imageLoader = ImageLoader()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        avatarView = view.findViewById(R.id.avatar)
        progressBar = view.findViewById(R.id.progress)
        loadProfile()
    }

    private fun loadProfile() {
        viewLifecycleOwner.lifecycleScope.launch {
            try {
                progressBar?.visibility = View.VISIBLE
                val bitmap = imageLoader.load("https://example.com/avatar.png")
                avatarView?.setImageBitmap(bitmap)
            } finally {
                progressBar?.visibility = View.GONE
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        avatarView = null
        progressBar = null
        imageLoader.cancel()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("ProfileFragment", "onDestroy: تم تدمير Fragment بالكامل")
    }
}

في onDestroyView، يتم تعيين مراجع View إلى null — هذا يمنع تسرب الذاكرة إذا كان closure في imageLoader يحتفظ بمرجع إلى avatarView. Fragment نفسه و ViewModel يبقيان على قيد الحياة حتى onDestroy. imageLoader.cancel() يلغي التحميل إذا غادر Fragment الشاشة.

مثال 3: التحقق من isFinishing في onDestroy

استخدام isFinishing() يسمح بالتمييز بين إنهاء Activity بأمر المستخدم أو لإعادة الإنشاء.

kotlin
class AnalyticsActivity : AppCompatActivity() {
    private val analytics = Analytics()

    override fun onDestroy() {
        if (isFinishing) {
            Log.d("AnalyticsActivity", "Activity تنتهي بـ finish() — إرسال التحليلات")
            analytics.sendSessionEnd()
        } else {
            Log.d("AnalyticsActivity", "Activity يتم إعادة إنشاؤها (تدوير/إعدادات) — لا يتم إرسال التحليلات")
        }
        super.onDestroy()
    }
}

التحقق من isFinishing() هو نمط مهم للتحليلات وتسجيل البيانات وتنظيف بيانات الجلسة. أثناء التدوير، لا ينبغي إرسال أحداث إنهاء الجلسة — المستخدم لا يزال يعمل مع التطبيق. وفقًا لـ Google Analytics، التحقق غير الصحيح من isFinishing() هو سبب 40% من أحداث الجلسة الخاطئة.

الأسئلة الشائعة

هل يمكن ألا يتم استدعاء onDestroy؟

نعم، يمكن — أثناء موت العملية من قبل النظام، أو الإيقاف القسري من قبل المستخدم، أو الإنهاء غير الطبيعي. وفقًا لجوجل، حوالي 5–8% من إنهاءات Activity تحدث دون استدعاء onDestroy. لا يجب على المطورين الاعتماد على onDestroy لحفظ البيانات الحرجة — استخدم onPause أو onSaveInstanceState.

ما الفرق بين onDestroy و finish()؟

finish() — استدعاء يبدأ تدمير Activity. onDestroy — رد اتصال (callback) يتم استدعاؤه أثناء تنفيذ finish(). finish() ضروري لاستدعاء onDestroy أثناء الإنهاء الطبيعي. finish() يمكن استدعاؤه من قبل النظام أو المطور، onDestroy هو فقط رد اتصال نظامي.

هل أحتاج إلى استدعاء super.onDestroy() في Fragment؟

نعم، بالتأكيد في كل من Activity و Fragment. super.onDestroy() يضمن التنظيف الصحيح لـ ChildFragmentManager و LoaderManager ومكونات النظام الأخرى. تخطي super.onDestroy() يؤدي إلى تسرب الذاكرة وأخطاء في استعادة الأجزاء.

متى يتم استدعاء onCleared() في ViewModel بالنسبة لـ onDestroy؟

onCleared() يُستدعى بعد onDestroy لـ Activity أو Fragment، عندما لا يكون ViewModel مطلوبًا بعد الآن. أثناء تدوير الشاشة، لا يُستدعى onCleared() — ViewModel يبقى بعد onDestroy. الترتيب: onDestroy لـ Activity/Fragment → (ViewModelStore يُنظف) → onCleared().

هل يمكنني بدء Service من onDestroy؟

نعم تقنيًا، لكن لا يُنصح بذلك. Activity تُدمر فورًا بعد onDestroy، ويبقى Service المُبدأ دون تحكم. للمهام الخلفية، استخدم WorkManager مع تأخير: WorkManager يضمن التنفيذ حتى بعد إنهاء Activity ويبقى بعد موت العملية.

الملخص

  • onDestroy — رد اتصال دورة الحياة النهائي لـ Activity و Fragment، يُستدعى قبل التدمير الكامل للمكون.
  • استدعاء onDestroy غير مضمون أثناء موت العملية — حوالي 5–8% من الإنهاءات تحدث بدونه.
  • في onDestroy يجب تحرير: ردود اتصال الشبكة، المقابس، تدفقات الملفات، BroadcastReceiver، ContentObserver.
  • ViewModel.onCleared() يُستدعى بعد onDestroy لـ Activity — viewModelScope يُلغى تلقائيًا.
  • onDestroyView في Fragment (منفصل عن onDestroy) — المكان المناسب لإلغاء مراجع View.
  • التحقق من isFinishing() في onDestroy يسمح بالتمييز بين إنهاء finish() وإعادة الإنشاء بسبب تغييرات الإعدادات.
  • لا تعتمد على onDestroy لحفظ البيانات — استخدم onPause أو onSaveInstanceState.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا