onPause: ما هو، حفظ حالة Activity في Android

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

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

الخلاصة

  • onPause — يفقد Activity التركيز ولكنه يظل مرئيًا؛ آخر نقطة مضمونة لحفظ البيانات
  • حفظ الحالة — في onPause يتم حفظ بيانات المستخدم الحرجة: المسودات، النص في النماذج، التقدم
  • تحرير الموارد — الكاميرا، الميكروفون، مشغل الفيديو يتم تحريرها في onPause لنقلها إلى تطبيق آخر
  • حد الوقت — يجب أن يكتمل onPause في غضون 100 مللي ثانية؛ تجاوز ذلك يسبب ANR ويؤخر الانتقال
  • SharedPreferences.apply() — كتابة غير متزامنة في onPause؛ commit() يحظر الخيط وقد يسبب ANR
  • onPause مقابل onStop — onPause عند الرؤية الجزئية (حوار)، onStop عند الإخفاء الكامل (Activity آخر)
  • onSaveInstanceState — يُستدعى بعد onPause لحفظ الحالة المؤقتة في Bundle

ما هو onPause في Android

onPause هي الطريقة الرابعة لدورة حياة Activity، والتي تُستدعى عندما تفقد الشاشة تركيز الإدخال ولكنها تظل مرئية جزئيًا للمستخدم. إنها حالة «انتقالية» بين تشغيل التطبيق النشط وإخفائه. يستدعي النظام onPause في السيناريوهات التالية: فتح Activity آخر (شاشة جديدة تغطي الشاشة الحالية جزئيًا)، ظهور مربع حوار (Dialog، PopupWindow، Snackbar لا تستدعي onPause، لكن DialogFragment يستدعيها)، الضغط على زر التطبيقات الحديثة، مكالمة واردة، الضغط على زر الطاقة لقفل الشاشة.

المهمة الرئيسية لـ onPause هي تحضير التطبيق لاحتمالية إخفائه أو تدميره. هذه هي آخر نقطة في دورة الحياة حيث يمكن للمطور أن يكون متأكدًا من تنفيذ كوده قبل أن يواصل النظام الانتقال إلى مكون آخر. بعد onPause، يستدعي النظام onStop (إذا تم إخفاء Activity بالكامل)، وبعد ذلك يمكن إنهاء العملية في أي وقت دون إشعار إضافي.

وفقًا لوثائق Android Developers (2025)، يجب أن يكون onPause خفيفًا وسريعًا قدر الإمكان. طالما أن onPause لم يُعد التحكم، لا يمكن للنظام بدء Activity التالي — مما يعني أن المستخدم يرى تأخيرًا في انتقال الشاشة. توصي Google بإكمال onPause في أقل من 100 مللي ثانية، ويجب تنفيذ جميع العمليات الطويلة (الحفظ في قاعدة البيانات، الكتابة على القرص) بشكل غير متزامن عبر coroutines أو apply().

onPause في Activity

في Activity، تُستدعى طريقة onPause في كل مرة تتوقف فيها الشاشة عن كونها نشطة ولكنها قد تستمر في العرض جزئيًا. مثال نموذجي: يفتح المستخدم تطبيق الخرائط، يضغط مشاركة الموقع، ويظهر حوار نظام لاختيار التطبيق فوق الخرائط. يتلقى Activity الخرائط onPause ولكنه يظل مرئيًا أسفل الحوار. عند إغلاق الحوار، تتلقى الخرائط onResume دون استدعاء onStart (لم يتم إخفاء الشاشة بالكامل).

kotlin
class NoteEditorActivity : AppCompatActivity() {
    private var binding: ActivityNoteEditorBinding? = null
    private val prefs by lazy {
        getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
    }

    override fun onPause() {
        super.onPause()

        // حفظ مسودة الملاحظة — بشكل غير متزامن
        prefs.edit()
            .putString("draft_title", binding?.titleInput?.text.toString())
            .putString("draft_body", binding?.bodyInput?.text.toString())
            .putLong("draft_timestamp", System.currentTimeMillis())
            .apply()

        // إيقاف الفيديو مؤقتًا
        binding?.videoPlayer?.pause()

        // تحرير الموارد الحصرية
        releaseCamera()
        releaseAudioFocus()
    }

    override fun onResume() {
        super.onResume()
        // استعادة المسودة
        binding?.titleInput?.setText(prefs.getString("draft_title", ""))
        binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
        acquireCamera()
        acquireAudioFocus()
    }
}

مثال NoteEditorActivity يوضح التعامل الصحيح مع onPause: حفظ مسودة في SharedPreferences عبر apply()، إيقاف ملف فيديو مؤقتًا، تحرير الكاميرا وتركيز الصوت. كل استدعاء خفيف وسريع، دون حظر خيط واجهة المستخدم لفترة كافية لإحداث ANR. لاحظ الترتيب: super.onPause() يُستدعى في السطر الأول — وهذا يضمن تنفيذ منطق النظام حتى في حالة حدوث استثناء في كود المستخدم.

حفظ الحالة في onPause

onPause هي آخر نقطة حيث يمكن للمطور حفظ بيانات المستخدم بشكل موثوق قبل إخفاء التطبيق أو إنهائه بواسطة النظام. بعد onStop، قد ينهي النظام العملية إذا كانت الذاكرة منخفضة، دون استدعاء onDestroy. تُستدعى طريقة onSaveInstanceState() بعد onPause، لكن Bundle الخاص بها غير مخصص للتخزين طويل الأمد — فهو يعيش فقط حتى onCreate التالي.

SharedPreferences مع apply()

SharedPreferences مع apply() غير المتزامن هي الطريقة المثلى لحفظ كميات صغيرة من البيانات في onPause. على عكس commit()، الذي يكتب البيانات بشكل متزامن على القرص ويعيد قيمة منطقية، يقوم apply() بحفظ البيانات فورًا في الذاكرة ويجدول كتابة غير متزامنة على القرص. يستغرق ذلك أقل من 1 مللي ثانية في خيط واجهة المستخدم مقابل 10–100 مللي ثانية لـ commit().

kotlin
override fun onPause() {
    super.onPause()

    // ❌ سيء: الكتابة المتزامنة تحظر الخيط
    // prefs.edit().putInt("score", score).commit()

    // ✅ جيد: الكتابة غير المتزامنة
    prefs.edit().putInt("score", score).apply()

    // للكائنات المعقدة — التخزين المؤقت في ViewModel
    viewModel.saveState()
}

Room و Coroutines

للبيانات المهيكلة (SQLite عبر Room) في onPause، تُستخدم coroutines مع lifecycleScope. يقوم ViewModelScope تلقائيًا بإلغاء coroutine عند تدمير ViewModel، مما يمنع الكتابة في قاعدة بيانات مغلقة. تستغرق الكتابة عبر Room مع coroutines 5–15 مللي ثانية ولا تحظر خيط واجهة المستخدم.

kotlin
// في ViewModel:
fun saveDraft(title: String, body: String) {
    viewModelScope.launch(Dispatchers.IO) {
        noteDao.insert(NoteDraft(title = title, body = body))
    }
}

// في Activity.onPause:
viewModel.saveDraft(
    binding?.titleInput?.text.toString(),
    binding?.bodyInput?.text.toString()
)

onPause في Fragment

onPause في Fragment يُستدعى عندما يتوقف Fragment عن كونه نشطًا ولكنه قد يظل مرئيًا. يحدث هذا عندما: يتم استبدال Fragment بـ Fragment آخر عبر FragmentTransaction؛ يتوقف Fragment عن كونه الصفحة الحالية في ViewPager؛ يتلقى Activity الذي يحتوي على Fragment onPause. التفاعل بين onPause لـ Activity و onPause لـ Fragment هرمي تمامًا: أولاً يتلقى Activity onPause، ثم جميع Fragments الخاصة به.

kotlin
class MapFragment : Fragment() {
    private var mapController: MapController? = null

    override fun onPause() {
        super.onPause()
        mapController?.stopFollowMode()
        binding?.mapContainer?.alpha = 0.7f
    }

    override fun onResume() {
        super.onResume()
        binding?.mapContainer?.alpha = 1.0f
        if (isVisible) {
            mapController?.startFollowMode()
        }
    }
}

خصوصية العمل مع الخرائط في onPause: تستهلك Google Maps و Yandex Maps موارد GPU كبيرة في وضع التتبع النشط. عند فقدان التركيز، من المنطقي تعطيل حركة الخريطة وتقليل تكرار تحديث العلامات، وعند استعادة التركيز، استعادة الوظائف الكاملة. هذا يحسن الأداء ويقلل استهلاك الطاقة عند التبديل بين الشاشات.

onPause مقابل onStop: الفرق والسيناريوهات

أحد أكثر الارتباكات شيوعًا بين مطوري Android المبتدئين هو عدم فهم الفرق بين onPause و onStop. دعنا نفحص كل سيناريو ونحدد الطريقة الصحيحة.

السيناريوonPauseonStop
فتح مربع حواريُستدعىلا يُستدعى
فتح Activity جديد (غير شفاف)يُستدعىيُستدعى
الضغط على زر الصفحة الرئيسيةيُستدعىيُستدعى
قفل الشاشةيُستدعىيُستدعى
مكالمة واردةيُستدعىيُستدعى
Activity شفاف فوق الحالييُستدعىلا يُستدعى
شاشة مقسمة (نصف شاشة)يُستدعىلا يُستدعى
PiP (صورة داخل صورة)يُستدعىلا يُستدعى

القاعدة الرئيسية: onPause يُستدعى عند أي فقدان للتركيز، onStop يُستدعى فقط عند فقدان الرؤية بالكامل. إذا بقي Activity مرئيًا (حتى جزئيًا)، لا يُستدعى onStop. هذا مهم بشكل حاسم لوضعي الشاشة المقسمة و PiP و Activity الشفاف — هنا يعمل onPause/onResume، لكن onStart/onStop لا يعملان.

توقيت وأداء onPause

onPause هي أكثر طرق دورة الحياة حرجًا من حيث الوقت، لأنها تحظر عرض Activity التالي. ينتظر النظام اكتمال onPause لـ Activity الحالي قبل عرض الجديد. إذا استغرق onPause أكثر من 100 مللي ثانية، يلاحظ المستخدم تأخيرًا في الانتقال؛ إذا استغرق أكثر من 5 ثوانٍ، يعرض النظام ANR.

توصيات الأداء

يقدم دليل أداء Android من Google (2025) التوصيات التالية لـ onPause: لا تقم بتنفيذ طلبات الشبكة — يجب إلغاؤها أو نقلها إلى WorkManager؛ لا تكتب ملفات كبيرة على القرص — استخدم BufferedWriter في خيط خلفية؛ لا تنفذ استعلامات SQL معقدة — يجب أن تكون عمليات Room غير متزامنة عبر coroutines؛ تجنب إنشاء كائنات جديدة — جمع القمامة في onPause يزيد التأخير سوءًا؛ استخدم apply() بدلاً من commit() لـ SharedPreferences.

kotlin
override fun onPause() {
    super.onPause()

    // ❌ سيء: طلب HTTP يحظر واجهة المستخدم
    // val response = api.syncSave(data).execute()

    // ❌ سيء: الكتابة المتزامنة في ملف
    // FileOutputStream(file).write(data)

    // ✅ جيد: الحفظ غير المتزامن
    lifecycleScope.launch {
        withContext(Dispatchers.IO) {
            api.saveData(data)
            fileDao.write(data)
        }
    }

    // ✅ جيد: كتابة خفيفة في SharedPreferences
    prefs.edit().putString("key", value).apply()
}

يظهر تحليل أداء onPause عبر Android Studio Profiler (تتبع وحدة المعالجة المركزية) وقت التنفيذ الدقيق. إذا استغرق onPause أكثر من 100 مللي ثانية، يبرز Profiler الطريقة باللون الأصفر، وأكثر من 500 مللي ثانية باللون الأحمر. في المشاريع التجارية لـ IT Sectr، نستخدم اختبارات Macrobenchmark التي تتحقق تلقائيًا من وقت الانتقال بين Activities وتشير إلى تراجعات الأداء في خط أنابيب CI.

الأخطاء الشائعة في onPause

حتى المطورون ذوو الخبرة يرتكبون أخطاء في onPause. دعنا نفحص خمس مشاكل نموذجية وحلولها.

الكتابة المتزامنة في قاعدة البيانات

استدعاء Room DAO باستعلام متزامن (.executeAsObservable() بدون coroutines) في onPause يحظر خيط واجهة المستخدم لمدة 10–50 مللي ثانية. إذا حدث في تلك اللحظة GC أو تنافس على الكتابة، قد يصل التأخير إلى 200–500 مللي ثانية. الحل: استخدم coroutines مع Dispatchers.IO أو apply() لـ SharedPreferences.

تسجيل مستمعين جدد

onPause ليس مكانًا لتسجيل المستمعين. إذا سجلت BroadcastReceiver في onPause، سيظل نشطًا عندما لا يكون Activity مرئيًا. يجب أن يكون التسجيل فقط في onStart/onResume، وفي onPause/onStop — فقط إلغاء التسجيل. الاستثناء هو واجهات برمجة التطبيقات القائمة على Intent التي تتطلب التسجيل قبل الاستدعاء.

تجاهل الاستثناءات

إذا حدث استثناء غير معالج في onPause، لا يستدعي النظام onStop و onDestroy. يتجمد Activity في حالة غير محددة، وقد لا يستعيد onResumed عند العودة الموارد المحررة بشكل صحيح. الحل: لف العمليات الحرجة في try/catch مع التسجيل عبر Log.e().

حفظ بيانات زائدة

ليست هناك حاجة لحفظ البيانات في onPause التي يمكن استعادتها بسهولة. على سبيل المثال، يتم تخزين نتائج طلبات API مؤقتًا في Room أو DataStore في وقت الحصول عليها، وليس في onPause. احفظ فقط ما أدخله المستخدم يدويًا ولا يمكن استعادته تلقائيًا — النص في الحقول، العناصر المحددة، موضع التمرير.

نسيان super.onPause()

يجب استدعاء super.onPause()، ولكن على عكس onCreate، فإن حذفه لا يتسبب في تعطل فوري. النظام «يتسامح» مع فقدان super في onPause، لكن آلة الحالة الداخلية تدخل في حالة غير صحيحة. قد لا يستعيد استدعاء onResume التالي تركيز الإدخال، تاركًا Activity «مجمدًا». استدعِ super.onPause() في أقرب وقت ممكن.

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

ماذا يحدث إذا تم استدعاء finish() في onPause؟

استدعاء finish() في onPause سينهي Activity فورًا بعد العودة من الطريقة. هذا سيناريو صحيح إذا كانت الشاشة بحاجة إلى الإغلاق عند فقدان التركيز (على سبيل المثال، شاشة التفويض عند تصغير التطبيق). ومع ذلك، يؤدي finish() إلى تشغيل دورة إنهاء كاملة: onStop onDestroy، مما يضيف تأخيرًا إلى الانتقال. استخدم finish() في onPause فقط عندما يكون ذلك ضروريًا حقًا.

كيف يختلف onPause عن onSaveInstanceState؟

onPause هو لحفظ البيانات التي يجب أن تبقى بعد إنهاء العملية (المسودات في SharedPreferences/Room). onSaveInstanceState هو لحفظ حالة واجهة المستخدم المؤقتة المطلوبة فقط حتى onCreate التالي (موضع التمرير، علامة التبويب المحددة). لا يتم الاحتفاظ بـ Bundle الخاص بـ onSaveInstanceState عند إنهاء التطبيق بالكامل — فهو موجود فقط في الذاكرة. يتم حفظ بيانات onPause على القرص وتظل بعد إعادة التشغيل.

هل يمكن فتح حوار في onPause؟

لا يُنصح بذلك. فتح حوار أو نافذة منبثقة في onPause يؤدي إلى WindowLeakException إذا كان Activity قد انتهى بالفعل. إذا كنت بحاجة إلى إظهار إشعار عند فقدان التركيز، استخدم NotificationManager (إشعارات النظام) — هذا آمن ومتوقع من قبل المستخدم. للإجراءات المؤجلة، استخدم AlarmManager أو WorkManager.

لماذا onPause هي نقطة حفظ مضمونة و onStop ليست كذلك؟

من المضمون استدعاء onPause قبل أن يتوقف Activity عن كونه نشطًا. قد لا يتم استدعاء onStop إذا قام النظام بقتل العملية لتحرير الذاكرة — في هذه الحالة، لا يتم استدعاء onDestroy أيضًا. onPause هي الطريقة الوحيدة بعد onResume التي تُستدعى دائمًا، بغض النظر عن سبب فقدان التركيز. لذلك، يتم حفظ جميع البيانات الحرجة تحديدًا في onPause.

كيفية اختبار onPause في اختبارات الوحدة؟

لاختبار onPause، يتم استخدام Robolectric أو FragmentScenario من AndroidX Test. FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED) تستدعي onPause بالتسلسل. ثم يتم التحقق من حفظ البيانات في SharedPreferences أو تحرير الكاميرا عبر كائن وهمي. يدعم Robolectric 4.12+ محاكاة onPause/onResume بدون جهاز فعلي.

الملخص

  • onPause — يفقد Activity تركيز الإدخال ولكنه يظل مرئيًا جزئيًا؛ آخر نقطة مضمونة لحفظ البيانات
  • الحفظ — SharedPreferences.apply() أو Room عبر coroutines؛ commit() والعمليات المتزامنة محظورة
  • تحرير الموارد — الكاميرا، تركيز الصوت، مشغل الفيديو يتم تحريرها في onPause لنقلها إلى تطبيق آخر
  • حد 100 مللي ثانية — onPause يحظر عرض Activity التالي؛ تجاوز الحد يسبب ANR
  • onPause مقابل onStop — onPause عند فقدان التركيز (الرؤية محفوظة)، onStop عند الإخفاء الكامل
  • Fragment.onPause — استدعاء هرمي بعد Activity.onPause؛ خصوصية للخرائط و ViewPager
  • الأخطاء الشائعة — الكتابة المتزامنة، تسجيل المستمعين، تجاهل try/catch، الحفظ الزائد
  • super.onPause() — استدعِ في أقرب وقت ممكن؛ حذفه لا يتسبب في تعطل لكنه يفسد آلة الحالة

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

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

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

اقرأ أيضًا