onPause هي طريقة من دورة حياة Android يتم استدعاؤها عندما يفقد Activity تركيز الإدخال ولكنه يظل مرئيًا جزئيًا على الشاشة. يستدعي النظام onPause قبل أن ينتقل Activity جديد إلى المقدمة، عند فتح مربع حوار، عند الضغط على زر التطبيقات الحديثة، أو عند ورود مكالمة واردة. هذه الطريقة هي آخر نقطة مضمونة لحفظ بيانات المستخدم، لأنه بعد onStop و onDestroy يمكن للنظام إنهاء العملية دون استدعاءات إضافية. داخل onPause، يحفظ المطور المسودات، ويوقف الرسوم المتحركة، ويحرر الكاميرا، ويكتب حالة واجهة المستخدم الحالية في SharedPreferences. لمزيد من التفاصيل حول دورة حياة Activity الكاملة، اقرأ المقال Activity Lifecycle.
الخلاصة
onPause هي الطريقة الرابعة لدورة حياة Activity، والتي تُستدعى عندما تفقد الشاشة تركيز الإدخال ولكنها تظل مرئية جزئيًا للمستخدم. إنها حالة «انتقالية» بين تشغيل التطبيق النشط وإخفائه. يستدعي النظام onPause في السيناريوهات التالية: فتح Activity آخر (شاشة جديدة تغطي الشاشة الحالية جزئيًا)، ظهور مربع حوار (Dialog، PopupWindow، Snackbar لا تستدعي onPause، لكن DialogFragment يستدعيها)، الضغط على زر التطبيقات الحديثة، مكالمة واردة، الضغط على زر الطاقة لقفل الشاشة.
المهمة الرئيسية لـ onPause هي تحضير التطبيق لاحتمالية إخفائه أو تدميره. هذه هي آخر نقطة في دورة الحياة حيث يمكن للمطور أن يكون متأكدًا من تنفيذ كوده قبل أن يواصل النظام الانتقال إلى مكون آخر. بعد onPause، يستدعي النظام onStop (إذا تم إخفاء Activity بالكامل)، وبعد ذلك يمكن إنهاء العملية في أي وقت دون إشعار إضافي.
وفقًا لوثائق Android Developers (2025)، يجب أن يكون onPause خفيفًا وسريعًا قدر الإمكان. طالما أن onPause لم يُعد التحكم، لا يمكن للنظام بدء Activity التالي — مما يعني أن المستخدم يرى تأخيرًا في انتقال الشاشة. توصي Google بإكمال onPause في أقل من 100 مللي ثانية، ويجب تنفيذ جميع العمليات الطويلة (الحفظ في قاعدة البيانات، الكتابة على القرص) بشكل غير متزامن عبر coroutines أو apply().
في Activity، تُستدعى طريقة onPause في كل مرة تتوقف فيها الشاشة عن كونها نشطة ولكنها قد تستمر في العرض جزئيًا. مثال نموذجي: يفتح المستخدم تطبيق الخرائط، يضغط مشاركة الموقع، ويظهر حوار نظام لاختيار التطبيق فوق الخرائط. يتلقى Activity الخرائط onPause ولكنه يظل مرئيًا أسفل الحوار. عند إغلاق الحوار، تتلقى الخرائط onResume دون استدعاء onStart (لم يتم إخفاء الشاشة بالكامل).
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 هي آخر نقطة حيث يمكن للمطور حفظ بيانات المستخدم بشكل موثوق قبل إخفاء التطبيق أو إنهائه بواسطة النظام. بعد onStop، قد ينهي النظام العملية إذا كانت الذاكرة منخفضة، دون استدعاء onDestroy. تُستدعى طريقة onSaveInstanceState() بعد onPause، لكن Bundle الخاص بها غير مخصص للتخزين طويل الأمد — فهو يعيش فقط حتى onCreate التالي.
SharedPreferences مع apply() غير المتزامن هي الطريقة المثلى لحفظ كميات صغيرة من البيانات في onPause. على عكس commit()، الذي يكتب البيانات بشكل متزامن على القرص ويعيد قيمة منطقية، يقوم apply() بحفظ البيانات فورًا في الذاكرة ويجدول كتابة غير متزامنة على القرص. يستغرق ذلك أقل من 1 مللي ثانية في خيط واجهة المستخدم مقابل 10–100 مللي ثانية لـ commit().
override fun onPause() {
super.onPause()
// ❌ سيء: الكتابة المتزامنة تحظر الخيط
// prefs.edit().putInt("score", score).commit()
// ✅ جيد: الكتابة غير المتزامنة
prefs.edit().putInt("score", score).apply()
// للكائنات المعقدة — التخزين المؤقت في ViewModel
viewModel.saveState()
}
للبيانات المهيكلة (SQLite عبر Room) في onPause، تُستخدم coroutines مع lifecycleScope. يقوم ViewModelScope تلقائيًا بإلغاء coroutine عند تدمير ViewModel، مما يمنع الكتابة في قاعدة بيانات مغلقة. تستغرق الكتابة عبر Room مع coroutines 5–15 مللي ثانية ولا تحظر خيط واجهة المستخدم.
// في 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 يُستدعى عندما يتوقف Fragment عن كونه نشطًا ولكنه قد يظل مرئيًا. يحدث هذا عندما: يتم استبدال Fragment بـ Fragment آخر عبر FragmentTransaction؛ يتوقف Fragment عن كونه الصفحة الحالية في ViewPager؛ يتلقى Activity الذي يحتوي على Fragment onPause. التفاعل بين onPause لـ Activity و onPause لـ Fragment هرمي تمامًا: أولاً يتلقى Activity onPause، ثم جميع Fragments الخاصة به.
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 كبيرة في وضع التتبع النشط. عند فقدان التركيز، من المنطقي تعطيل حركة الخريطة وتقليل تكرار تحديث العلامات، وعند استعادة التركيز، استعادة الوظائف الكاملة. هذا يحسن الأداء ويقلل استهلاك الطاقة عند التبديل بين الشاشات.
أحد أكثر الارتباكات شيوعًا بين مطوري Android المبتدئين هو عدم فهم الفرق بين onPause و onStop. دعنا نفحص كل سيناريو ونحدد الطريقة الصحيحة.
| السيناريو | onPause | onStop |
|---|---|---|
| فتح مربع حوار | يُستدعى | لا يُستدعى |
| فتح Activity جديد (غير شفاف) | يُستدعى | يُستدعى |
| الضغط على زر الصفحة الرئيسية | يُستدعى | يُستدعى |
| قفل الشاشة | يُستدعى | يُستدعى |
| مكالمة واردة | يُستدعى | يُستدعى |
| Activity شفاف فوق الحالي | يُستدعى | لا يُستدعى |
| شاشة مقسمة (نصف شاشة) | يُستدعى | لا يُستدعى |
| PiP (صورة داخل صورة) | يُستدعى | لا يُستدعى |
القاعدة الرئيسية: onPause يُستدعى عند أي فقدان للتركيز، onStop يُستدعى فقط عند فقدان الرؤية بالكامل. إذا بقي Activity مرئيًا (حتى جزئيًا)، لا يُستدعى onStop. هذا مهم بشكل حاسم لوضعي الشاشة المقسمة و PiP و Activity الشفاف — هنا يعمل onPause/onResume، لكن onStart/onStop لا يعملان.
onPause هي أكثر طرق دورة الحياة حرجًا من حيث الوقت، لأنها تحظر عرض Activity التالي. ينتظر النظام اكتمال onPause لـ Activity الحالي قبل عرض الجديد. إذا استغرق onPause أكثر من 100 مللي ثانية، يلاحظ المستخدم تأخيرًا في الانتقال؛ إذا استغرق أكثر من 5 ثوانٍ، يعرض النظام ANR.
يقدم دليل أداء Android من Google (2025) التوصيات التالية لـ onPause: لا تقم بتنفيذ طلبات الشبكة — يجب إلغاؤها أو نقلها إلى WorkManager؛ لا تكتب ملفات كبيرة على القرص — استخدم BufferedWriter في خيط خلفية؛ لا تنفذ استعلامات SQL معقدة — يجب أن تكون عمليات Room غير متزامنة عبر coroutines؛ تجنب إنشاء كائنات جديدة — جمع القمامة في onPause يزيد التأخير سوءًا؛ استخدم apply() بدلاً من commit() لـ SharedPreferences.
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. دعنا نفحص خمس مشاكل نموذجية وحلولها.
استدعاء 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()، ولكن على عكس onCreate، فإن حذفه لا يتسبب في تعطل فوري. النظام «يتسامح» مع فقدان super في onPause، لكن آلة الحالة الداخلية تدخل في حالة غير صحيحة. قد لا يستعيد استدعاء onResume التالي تركيز الإدخال، تاركًا Activity «مجمدًا». استدعِ super.onPause() في أقرب وقت ممكن.
الأسئلة الشائعة
استدعاء finish() في onPause سينهي Activity فورًا بعد العودة من الطريقة. هذا سيناريو صحيح إذا كانت الشاشة بحاجة إلى الإغلاق عند فقدان التركيز (على سبيل المثال، شاشة التفويض عند تصغير التطبيق). ومع ذلك، يؤدي finish() إلى تشغيل دورة إنهاء كاملة: onStop onDestroy، مما يضيف تأخيرًا إلى الانتقال. استخدم finish() في onPause فقط عندما يكون ذلك ضروريًا حقًا.
onPause هو لحفظ البيانات التي يجب أن تبقى بعد إنهاء العملية (المسودات في SharedPreferences/Room). onSaveInstanceState هو لحفظ حالة واجهة المستخدم المؤقتة المطلوبة فقط حتى onCreate التالي (موضع التمرير، علامة التبويب المحددة). لا يتم الاحتفاظ بـ Bundle الخاص بـ onSaveInstanceState عند إنهاء التطبيق بالكامل — فهو موجود فقط في الذاكرة. يتم حفظ بيانات onPause على القرص وتظل بعد إعادة التشغيل.
لا يُنصح بذلك. فتح حوار أو نافذة منبثقة في onPause يؤدي إلى WindowLeakException إذا كان Activity قد انتهى بالفعل. إذا كنت بحاجة إلى إظهار إشعار عند فقدان التركيز، استخدم NotificationManager (إشعارات النظام) — هذا آمن ومتوقع من قبل المستخدم. للإجراءات المؤجلة، استخدم AlarmManager أو WorkManager.
من المضمون استدعاء onPause قبل أن يتوقف Activity عن كونه نشطًا. قد لا يتم استدعاء onStop إذا قام النظام بقتل العملية لتحرير الذاكرة — في هذه الحالة، لا يتم استدعاء onDestroy أيضًا. onPause هي الطريقة الوحيدة بعد onResume التي تُستدعى دائمًا، بغض النظر عن سبب فقدان التركيز. لذلك، يتم حفظ جميع البيانات الحرجة تحديدًا في onPause.
لاختبار onPause، يتم استخدام Robolectric أو FragmentScenario من AndroidX Test. FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED) تستدعي onPause بالتسلسل. ثم يتم التحقق من حفظ البيانات في SharedPreferences أو تحرير الكاميرا عبر كائن وهمي. يدعم Robolectric 4.12+ محاكاة onPause/onResume بدون جهاز فعلي.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.