توفِّر كوروتيني كوتلين واجهة برمجة تطبيقات تتيح لك كتابة
رمز غير متزامن. باستخدام الكوروتينات في لغة Kotlin، يمكنك تحديد CoroutineScope
الذي يساعدك في إدارة وقت تشغيل الكوروتينات. وتعمل كل عملية غير متزامنة ضمن نطاق معين.
توفّر المكوّنات الواعية لدورة الحياة
دعمًا من الدرجة الأولى للكورروتينات للنطاقات المنطقية في تطبيقك، بالإضافة إلى
طبقة إمكانية التشغيل التفاعلي مع LiveData
.
يشرح هذا الموضوع كيفية استخدام الكوروتين بشكل فعّال مع المكوّنات التي تراعي مراحل النشاط.
إضافة تبعيات KTX
إنّ نطاقات الكوروتين المدمجة الموضّحة في هذا الموضوع مضمّنة في إضافات KTX لكل مكوّن مقابل. تأكد من إضافة التبعيات المناسبة عند استخدام هذه النطاقات.
- بالنسبة إلى
ViewModelScope
، استخدِمandroidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
أو إصدارًا أحدث. - بالنسبة إلى
LifecycleScope
، استخدِمandroidx.lifecycle:lifecycle-runtime-ktx:2.4.0
أو إصدارًا أحدث. - بالنسبة إلى
liveData
، استخدِمandroidx.lifecycle:lifecycle-livedata-ktx:2.4.0
أو إصدارًا أحدث.
نطاقات الكوروتين الواعية لمراحل الحياة
تحدِّد المكونات الواعية لدورة الحياة النطاقات المضمَّنة التالية التي يمكنك استخدامها في تطبيقك.
نطاق نموذج العرض
يتم تحديد ViewModelScope
لكل
ViewModel
في تطبيقك. ويتم تلقائيًا إلغاء أي
كورروتين تم إطلاقه في هذا النطاق في حال محو ViewModel
. ويمكن الاستفادة من الكوروتين هنا عندما يكون لديك عمل يجب إنجازه فقط إذا كان ViewModel
مفعَّلاً. على سبيل المثال، إذا كنت تحتسب بعض البيانات
لتنسيق، يجب تحديد نطاق العمل على ViewModel
بحيث إذا تم محو ViewModel
، يتم إلغاء العمل تلقائيًا لتجنّب استهلاك الموارد.
يمكنك الوصول إلى CoroutineScope
لـ ViewModel
من خلال السمة
viewModelScope
في ViewModel، كما هو موضّح في المثال التالي:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
نطاق مراحل النشاط
يتم تحديد LifecycleScope
لكل عنصر من عناصر
Lifecycle
. يتم إلغاء أي الكوروتين تم إطلاقه في هذا النطاق عند تدمير Lifecycle
. ويمكنك
الوصول إلى CoroutineScope
من Lifecycle
إما من خلال
lifecycle.coroutineScope
أو lifecycleOwner.lifecycleScope
.
يوضح المثال أدناه كيفية استخدام lifecycleOwner.lifecycleScope
لإنشاء نص محسوب مسبقًا بشكل غير متزامن:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
فيديوهات كورروتينية قابلة لإعادة التشغيل مراعية لمراحل الحياة
على الرغم من أنّ lifecycleScope
توفّر طريقة مناسبة لإلغاء العمليات طويلة المدى تلقائيًا عندما تكون قيمة Lifecycle
هي DESTROYED
، قد تكون لديك حالات أخرى قد تريد فيها بدء تنفيذ حظر الرموز عندما يكون Lifecycle
في حالة معيّنة، وإلغائه إذا كان في حالة أخرى. على سبيل المثال، يمكنك جمع تدفق عندما تكون قيمة Lifecycle
هي STARTED
وإلغاء المجموعة عندما تكون STOPPED
. ويعمل هذا النهج على معالجة انبعاثات التدفق فقط عند ظهور واجهة المستخدم على الشاشة،
لتوفير الموارد وربما تجنُّب أعطال التطبيق.
في هذه الحالات، يوفّر Lifecycle
وLifecycleOwner
واجهة برمجة تطبيقات
repeatOnLifecycle
المعلّقة التي تنفِّذ هذا الإجراء بالضبط. يتضمّن المثال التالي مجموعة رموز يتم تشغيلها في كل مرة تكون فيها سمة Lifecycle
المرتبطة في حالة STARTED
على الأقل ويتم إلغاؤها عندما تكون قيمة Lifecycle
STOPPED
:
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
}
}
جمع التدفق الواعي لمراحل الحياة
إذا كنت تحتاج فقط إلى تنفيذ جمع البيانات الواعية بدورة الحياة في مسار واحد، يمكنك
استخدام طريقة
Flow.flowWithLifecycle()
لتبسيط الرمز:
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
ومع ذلك، إذا كنت بحاجة إلى إجراء جمع واعٍ لدورة الحياة على تدفقات متعددة
بتوالي، يجب عليك جمع كل تدفق في الكوروتينات المختلفة. في هذه الحالة، يكون استخدام repeatOnLifecycle()
مباشرةً أكثر فعالية:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Because collect is a suspend function, if you want to
// collect multiple flows in parallel, you need to do so in
// different coroutines.
launch {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
تعليق الكوروتينات الواعية لمراحل الحياة
على الرغم من أنّ CoroutineScope
توفّر طريقة مناسبة لإلغاء العمليات طويلة المدى تلقائيًا، قد يكون لديك حالات أخرى تريد فيها تعليق تنفيذ مجموعة الرموز ما لم يكن Lifecycle
في حالة معيّنة. على سبيل المثال، لتشغيل FragmentTransaction
، عليك الانتظار حتى تصبح قيمة Lifecycle
STARTED
على الأقل. في هذه الحالات، توفِّر السمة Lifecycle
طُرقًا إضافية: lifecycle.whenCreated
وlifecycle.whenStarted
وlifecycle.whenResumed
. ويتمّ تعليق أي تشغيل لكروتين داخل هذه الوحدات إذا لم تكن قيمة Lifecycle
في الحد الأدنى المطلوب على الأقل.
يتضمّن المثال أدناه مجموعة رموز لا يتم تشغيلها إلا عندما تكون قيمة Lifecycle
المرتبطة في حالة STARTED
على الأقل:
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
إذا تم تدمير Lifecycle
أثناء نشاط الكوروتين من خلال إحدى
الطرق when
، يتم إلغاء الكوروتين تلقائيًا. في المثال أدناه،
يتم تشغيل مجموعة finally
بعد أن تصبح حالة Lifecycle
هي DESTROYED
:
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
استخدام الكوروتينات مع LiveData
عند استخدام LiveData
، قد تحتاج إلى
حساب القيم بشكل غير متزامن. على سبيل المثال، قد ترغب في استرداد تفضيلات
المستخدم وعرضها على واجهة المستخدم لديك. في
هذه الحالات، يمكنك استخدام دالة إنشاء liveData
لاستدعاء الدالة suspend
،
وتقديم النتيجة ككائن LiveData
.
في المثال أدناه، loadUser()
هي دالة تعليق تم تعريفها في مكان آخر. استخدم
دالة أداة إنشاء liveData
لاستدعاء loadUser()
بشكل غير متزامن، ثم
استخدم emit()
لإرسال النتيجة:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
إنّ الوحدة الأساسية لـ liveData
هي بمثابة
مجموعة أولية منظمّة للمزامنة
بين الكوروتينات وLiveData
. يبدأ تنفيذ مجموعة الرموز عندما
يصبح LiveData
نشطًا ويتم إلغاؤه تلقائيًا بعد انتهاء مهلة
قابلة للضبط عندما يصبح LiveData
غير نشط. وإذا تم إلغاؤه قبل الإكمال، تتم إعادة تشغيله إذا أصبح LiveData
نشطًا مرة أخرى. إذا تم إكمالها بنجاح في
تشغيل سابق، فلن تتم إعادة تشغيلها. تجدر الإشارة إلى أنّه لا يمكن
إعادة تشغيلها إلا إذا تم إلغاؤها تلقائيًا. إذا تم إلغاء الحظر لأي سبب آخر (مثل طرح CancellationException
)، لا تتم إعادة تشغيله.
يمكنك أيضًا إصدار قيم متعددة من المجموعة. عند استدعاء emit()
، يتم تعليق تنفيذ الحظر إلى أن يتم ضبط قيمة LiveData
على سلسلة التعليمات الرئيسية.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
يمكنك أيضًا دمج السمة liveData
مع السمة Transformations
، كما هو موضّح في المثال التالي:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
يمكنك إصدار قيَم متعدّدة من LiveData
عن طريق استدعاء الدالة emitSource()
متى أردت إصدار قيمة جديدة. لاحظ أن كل استدعاء إلى emit()
أو emitSource()
تزيل المصدر الذي تمت إضافته مسبقًا.
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
لمزيد من المعلومات المتعلقة بالكوروتينات، اطّلع على الروابط التالية:
- تحسين أداء التطبيق باستخدام الكوروتينات في لغة Kotlin
- نظرة عامة على الكوروتينات
- سلاسل المحادثات في CoroutineWorker
مراجع إضافية
لمعرفة المزيد حول استخدام الكوروتينات مع المكونات الواعية لدورة الحياة، راجع الموارد الإضافية التالية.
عيّنات
المدوّنات
- Coroutines على Android: أنماط التطبيقات
- عروض الكوروتين السهلة في Android: viewModelScope
- اختبار انبعاثين متتاليين لانبعاثات البيانات المباشرة في الكوروتينات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- نظرة عامة على LiveData
- التعامل مع مراحل النشاط باستخدام مكونات حسب مراحل النشاط
- تحميل البيانات المقسّمة على صفحات وعرضها