lifecycleScope — هو CoroutineScope مضمن من مكتبة androidx.lifecycle، مرتبط بدورة حياة Activity أو Fragment أو أي LifecycleOwner، ويلغي الكوروتينات تلقائياً عند تدمير المكون. وفقاً لـ Google Android Developers, 2025، يسمح lifecycleScope بتشغيل الكوروتينات بأمان في طبقة واجهة المستخدم دون خطر تنفيذ برمجية بعد تدمير Activity أو Fragment. يتم إلغاء السكوب تلقائياً عند انتقال LifecycleOwner إلى الحالة DESTROYED.
النقاط الرئيسية
lifecycleScope هو خاصية تمديد على واجهة LifecycleOwner (Activity، Fragment، Service) توفر CoroutineScope جاهزاً مرتبطاً بدورة حياة المكون بأكملها. عند وصول LifecycleOwner إلى الحالة DESTROYED، يلغي lifecycleScope جميع الكوروتينات النشطة تلقائياً.
// In Fragment or Activity
lifecycleScope.launch {
delay(1000)
showSnackbar("مرحباً!")
}
بالخلاف لـ viewModelScope، يتم إلغاء lifecycleScope كل مرة يتم فيها تدمير LifecycleOwner — بما في ذلك تدوير الشاشة. وهذا يجعله مثالياً للعمليات التي يجب أن تعيش فقط طالما كانت شاشة معينة مرئية.
lifecycleScope متوفر في كل مكان يوجد فيه LifecycleOwner:
تعتمد آلية الإلغاء التلقائي لـ lifecycleScope على الاشتراك في أحداث Lifecycle. عندما ينخفض Lifecycle أسفل من CREATED إلى DESTROYED، يتم إلغاء السكوب.
| الحالة | الوصف | السكوب نشط |
|---|---|---|
| CREATED | تم إنشاء LifecycleOwner، تم تنفيذ onCreate | نعم |
| STARTED | LifecycleOwner مرئي (onStart) | نعم |
| RESUMED | LifecycleOwner في المقدمة (onResume) | نعم |
| DESTROYED | تم تدمير LifecycleOwner (onDestroy) | لا (تم إلغاء السكوب) |
يتم إنشاء lifecycleScope كـ CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) ويتم تخزينه داخل Lifecycle. عند انتقال Lifecycle إلى الحالة DESTROYED، يتم استدعاء scope.cancel(). تم تنفيذ الآلية من خلال LifecycleEventObserver، الذي يشترك في أحداث دورة الحياة عند أول وصول إلى السكوب.
عند تدوير الشاشة، تتم تدمير Activity (onDestroy) وإنشاؤها من جديد. يتم إلغاء lifecycleScope مع Activity القديمة، ويتم إنشاء مثال جديد من السكوب لـ Activity الجديدة. هذا هو الفرق الأساسي عن viewModelScope، الذي يبقى عند التدوير.
توفر مكتبة lifecycle عدة طرق لتشغيل الكوروتينات من خلال lifecycleScope. دعنا نستعرض تطور API من الطرق القديمة إلى الحديثة.
أبسط طريقة هي lifecycleScope.launch { ... }. تبدأ الكوروتين فوراً وتلغى عند DESTROYED. ولكنها يمكنها تنفيذ برمجية حتى عندما لا تكون واجهة المستخدم مرئية (على سبيل المثال، في الخلفية بعد onStop). وهذا ليس مرغوباً دائماً.
كانت هذه الطرق توقف تنفيذ الكوروتين عندما ينخفض Lifecycle أسفل من الحالة المحددة وتستئنفها عند العودة. ولكنها وسمت بأنها @Deprecated في lifecycle-runtime-ktx 2.6.0 لأنها:
repeatOnLifecycle هو الطريقة الموصى بها من Google لتشغيل الكوروتينات المتزامنة مع دورة الحياة. يلغي ويعيد تشغيل الكوروتين كل مرة يصل فيها Lifecycle إلى الحالة المحددة.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
}
تبدأ الكوروتين الممررة إلى repeatOnLifecycle عندما يصل Lifecycle إلى STARTED وتلغى عندما ينخفض أسفل من STARTED. عند العودة إلى STARTED، تعاد تشغيل الكوروتين من جديد. هذا آمن وفعال — لا تبقى أي كوروتينات معلقة.
لجمع البيانات من Flow مع مراعاة دورة الحياة، هناك عامل flowWithLifecycle. يوقف ويستئنف الجمع تلقائياً عند تغير حالة Lifecycle:
viewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { state -> updateUI(state) }
.launchIn(lifecycleScope)
عامل flowWithLifecycle هو أكثر طريقة موجزة للاشتراك بأمان في Flow في طبقة واجهة المستخدم.
دعنا نستعرض ثلاث سيناريوهات واقعية لاستخدام lifecycleScope في تطبيق Android باستخدام Kotlin.
class MapFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
locationProvider.observeLocation().collect { loc ->
updateMapMarker(loc)
}
}
}
}
}
تبدأ الكوروتين عندما يصبح الفراغمنت مرئياً (STARTED) وتلغى عندما يخرج من الشاشة (STOPPED). إذا قام المستخدم بالتبديل إلى تطبيق آخر، فإن تحديثات الموقع لن تستهلك البطارية.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
animateFadeIn(titleView)
delay(200)
animateSlideUp(contentView)
}
}
تتم تشغيل الرسوم المتحركة فقط عندما يكون الفراغمنت في المقدمة (RESUMED). إذا قام المستخدم بتصغير التطبيق أثناء الرسوم المتحركة، يتم إلغاء الكوروتين، وعند العودة تعاد الرسوم المتحركة من جديد.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
while (isActive) {
syncData()
delay(30_000L)
}
}
}
يتم مزامنة البيانات كل 30 ثانية، ولكن فقط عندما تكون الشاشة مرئية. isActive تتحقق مما إذا كانت الكوروتين قد ألغيت، مما يوفر خروجاً آمناً من الحلقة عند مغادرة الشاشة.
كلا السكوبين مرتبطان بدورة الحياة، ولكن بجوانب مختلفة منها. فهم الفرق أمر حاسم لبناء معمارية صحيحة لتطبيقات Android.
viewModelScope مرتبط بـ ViewModel، الذي يبقى عند تدوير الشاشة. lifecycleScope مرتبط بـ LifecycleOwner (Activity/Fragment)، الذي يتم تدميره وإنشاؤه من جديد عند التدوير. وهذا يحدد سيناريوهات استخدامهما.
في الممارسة، يشيع مزيج من كلا السكوبين: viewModelScope يحمل البيانات ويدير الحالة، بينما lifecycleScope يشترك في Flow من ViewModel مع مراعاة دورة الحياة. يعتبر هذا الفصل بين المسؤوليات أفضل ممارسة في تطوير Android الحديث.
دعنا نستعرض أربعة من أكثر الأخطاء شيوعاً التي يرتكبها المطورون عند استخدام lifecycleScope.
إذا قمت بتشغيل تحميل البيانات في lifecycleScope.launch، ستلغى الكوروتين عند تدوير الشاشة، وسيتعين تحميل البيانات من جديد. استخدم viewModelScope للعمليات طويلة العمر. lifecycleScope مخصص فقط للمهام المرتبطة بواجهة المستخدم.
الاستدعاء المباشر viewModel.someFlow.collect { ... } داخل lifecycleScope.launch يستمر في جمع البيانات حتى عندما لا تكون الشاشة مرئية. يمكن أن يؤدي ذلك إلى تحديثات واجهة المستخدم في الخلفية وعبء غير ضروري. استخدم دائماً repeatOnLifecycle أو flowWithLifecycle.
على الرغم من أن lifecycleScope يلغى عند DESTROYED، قد لا يتم تنفيذ البرمجية بعد نقطة التعليق عند الإلغاء المفاجئ. لا تعتمد على تنفيذ البرمجية بعد استدعاء suspend إلا إذا كنت تستخدم NonCancellable.
launchWhenStarted ونظائره لا يلغون الكوروتين، بل يوقفونها فقط. إذا تبدلت الشاشة بين المقدمة والخلفية عدة مرات، تقوم الكوروتين بتراكم الاستدعاءات المؤجلة. انتقل إلى repeatOnLifecycle — هذا هو الطريقة الصحيحة الوحيدة للتزامن مع Lifecycle.
الأسئلة الشائعة
lifecycleScope يلغى تلقائياً عند تدمير LifecycleOwner. GlobalScope يعيش لطول مدة عمل التطبيق. لا يمكن للكوروتين في lifecycleScope تحديث واجهة المستخدم بعد تدمير المكون، بينما في GlobalScope يمكنه ذلك، مما يؤدي إلى أعطال. استخدم دائماً lifecycleScope في طبقة واجهة المستخدم.
لا، ViewModel ليس LifecycleOwner، لذا lifecycleScope غير متوفر فيه. ViewModel يستخدم viewModelScope. إذا كان البرمجية يجب أن تعمل في كلا السياقين، استخرج المنطق في use case أو repository مع دوال suspend.
كل استدعاء لـ repeatOnLifecycle ينشئ كوروتين جديد يقوم بتشغيل الكتلة عند الوصول إلى حالة Lifecycle المحددة. إذا تم استدعاء repeatOnLifecycle مرتين لنفس الحالة، ستتم تشغيل كلتا الكتلتين بشكل مستقل. عادةً، يكفي استدعاء واحد في onViewCreated.
لا يمكنك تغيير موزع lifecycleScope مباشرة — يستخدم Dispatchers.Main.immediate. داخل كتلة الكوروتين، يمكنك التبديل إلى موزع آخر من خلال withContext. للاختبارات، استخدم TestDispatcher مع LifecycleOwner.
يتم إلغاء lifecycleScope عند انتقال LifecycleOwner إلى الحالة DESTROYED (بعد onDestroy). لا يتم إلغاء استدعاءات lifecycleScope.launch البسيطة في onPause أو onStop. للإيقاف عند الذهاب إلى الخلفية، استخدم repeatOnLifecycle(STARTED) أو repeatOnLifecycle(RESUMED).
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا