الكوروتين هو نمط تصميم متزامن يمكنك استخدامه على تبسيط الرمز الذي يتم تنفيذه بشكل غير متزامن في Android الكوروتين إلى Kotlin في الإصدار 1.3 وتستند إلى المفاهيم من اللغات الأخرى.
على نظام Android، يساعد الكوروتين في إدارة المهام الطويلة المدى التي قد حظر سلسلة التعليمات الرئيسية وجعل التطبيق لا يستجيب. أفاد أكثر من 50% من المطوّرين المحترفين الذين يستخدمون الكوروتينات بأنهم زيادة الإنتاجية. يصف هذا الموضوع كيفية استخدام الكوروتينات في لغة Kotlin لمعالجة هذه مما يتيح لك كتابة كود تطبيق أكثر وضوحًا وإيجازًا.
الميزات
الكوروتينات هو الحل الذي ننصح به للبرمجة غير المتزامنة على Android وتشمل الميزات البارزة ما يلي:
- خفيفة الوزن: يمكنك تشغيل العديد من الكوروتينات في سلسلة محادثات واحدة بسبب الدعم لـ التعليق، وهذا لا يحظر سلسلة المحادثات التي يعمل بها الكوروتين. جارٍ التعليق ويحفظ الذاكرة خلال الحظر مع دعم العديد من العمليات المتزامنة.
- عدد أقل من تسريب الذاكرة: استخدم التزامن المنظّم لتشغيل العمليات ضمن النطاق.
- إمكانية إلغاء الاشتراك المضمَّنة: الإلغاء تلقائيًا من خلال تسلسل الكوروتينات الجاري.
- دمج Jetpack: تتضمن العديد من مكتبات Jetpack. الإضافات التي توفّر دعم الكوروتينات بالكامل بعض الإشعارات المكتبات أيضًا توفر نطاق الكوروتين الذي يمكنك للاستخدام في التزامن المنظم.
نظرة عامة على الأمثلة
استنادًا إلى دليل بنية التطبيق، يمكن العثور على في هذا الموضوع، أرسل طلبًا عبر الشبكة وارجع النتيجة إلى حيث يمكن للتطبيق عرض النتيجة للمستخدم.
وعلى وجه التحديد، ViewModel
يستدعي مكوِّن البنية طبقة المستودع في سلسلة التعليمات الرئيسية
تشغيل طلب الشبكة. يتكرر هذا الدليل عبر عدة حلول
التي تستخدم الكوروتينات لإبقاء سلسلة التعليمات الرئيسية غير محظورة.
تضم ViewModel
مجموعة من إضافات KTX التي تعمل مباشرةً مع
الكوروتينات. هذه الإضافات
مكتبة lifecycle-viewmodel-ktx
ويتم استخدامها
الواردة في هذا الدليل.
معلومات التبعية
لاستخدام الكوروتين في مشروع Android، أضِف الاعتمادية التالية:
إلى ملف build.gradle
في تطبيقك:
Groovy
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
التنفيذ في سلسلة محادثات في الخلفية
يؤدي تقديم طلب إلى الشبكة في سلسلة المحادثات الرئيسية إلى انتظارها أو حظرها،
حتى يتلقى ردًا. ولأن سلسلة المحادثات محظورة، فإن نظام التشغيل ليس
يمكنك الاتصال بـ onDraw()
، ما قد يؤدي إلى توقّف تطبيقك عن العمل وربما
إلى مربع الحوار "التطبيق لا يستجيب" (ANR). لتجربة مستخدم أفضل
في الخلفية، لنقم بإجراء هذه العملية على مؤشر ترابط في الخلفية.
أولاً، دعنا نلقي نظرة على صف Repository
ونرى
تقديم طلب الشبكة:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
تطبيق makeLoginRequest
متزامن ويحظر سلسلة المكالمات. للنموذج
استجابة طلب الشبكة، لدينا فئتنا الخاصة Result
.
يؤدي ViewModel
إلى تشغيل طلب الشبكة عندما ينقر المستخدم، للحصول على
على سبيل المثال، على زر:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
باستخدام الرمز السابق، تحظر LoginViewModel
سلسلة محادثات واجهة المستخدم عندما
إجراء طلب الشبكة. أبسط حلّ لتحريك التنفيذ
خارج سلسلة التعليمات الرئيسية هو إنشاء كوروتين جديد وتنفيذ بيانات
الطلب على سلسلة محادثات I/O:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
لنحلِّل رمز الكوروتينات في الدالة login
:
viewModelScope
هو عنصرCoroutineScope
محدَّد مسبقًا يتم تضمينه مع إضافات KTXViewModel
. لاحظ أنه يجب تشغيل جميع الكوروتينات في النطاق. يديرCoroutineScope
واحد أو أكثر من الكوروتينات ذات الصلة.launch
هي دالة تنشئ الكوروتين وإرسال تنفيذ نص وظيفته إلى المرسل المقابل.- تشير السمة
Dispatchers.IO
إلى أنّه يجب تنفيذ هذا الكوروتين على تم حجز سلسلة التعليمات لعمليات الإدخال والإخراج.
يتم تنفيذ الدالة login
على النحو التالي:
- يستدعي التطبيق الدالة
login
من طبقةView
في سلسلة المحادثات الرئيسية. - ينشئ
launch
كوروتينًا جديدًا، ويتم تنفيذ طلب الشبكة. بشكل مستقل على سلسلة محادثات محجوزة لعمليات الإدخال والإخراج. - أثناء تشغيل الكوروتين، تستمر وظيفة
login
في التنفيذ. وعمليات الإرجاع، ربما قبل الانتهاء من طلب الشبكة. لاحظ أن للتبسيط، يتم تجاهل استجابة الشبكة في الوقت الحالي.
بما أنّ هذا الكوروتين بدأ باستخدام viewModelScope
، يتم تنفيذه في
نطاق ViewModel
. إذا تم تلف ViewModel
بسبب
انتقال المستخدم بعيدًا عن الشاشة، يتم تلقائيًا نقل "viewModelScope
"
وإلغاء جميع الكوروتينات الجارية أيضًا.
إحدى مشكلات المثال السابق هي أن أي شيء يتصل
يحتاج makeLoginRequest
إلى تذكُّر نقل عملية التنفيذ بشكل صريح
سلسلة التعليمات الرئيسية. لنرَ كيف يمكننا تعديل Repository
لحلّ
هذه المشكلة لنا.
استخدام الكوروتين للحفاظ على السلامة الرئيسية
نعتبر الدالة main-safe عندما لا تحظر تحديثات واجهة المستخدم على
السلسلة الرئيسية. الدالة makeLoginRequest
ليست آمنة بشكل رئيسي، حيث إن استدعاء
تحظر سياسة makeLoginRequest
من سلسلة التعليمات الرئيسية واجهة المستخدم. يمكنك استخدام
دالة withContext()
من مكتبة الكوروتينات لنقل عملية التنفيذ
من الكوروتين إلى سلسلة محادثات مختلفة:
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO)
ينقل تنفيذ الكوروتين إلى
تعمل سلسلة وحدات الإدخال والإخراج على جعل وظيفة الاستدعاء آمنة بشكل رئيسي وتمكين واجهة المستخدم
تحديثه حسب الحاجة.
يتميز makeLoginRequest
أيضًا بالكلمة الرئيسية suspend
. هذه الكلمة الرئيسية
هي طريقة Kotlin لفرض دالة يتم استدعاؤها من داخل الكوروتين.
في المثال التالي، يتم إنشاء الكوروتين في LoginViewModel
.
بما أنّ makeLoginRequest
ينقل عملية التنفيذ خارج سلسلة التعليمات الرئيسية، يصبح الكوروتين
في الدالة login
، يمكن تنفيذها الآن في سلسلة التعليمات الرئيسية:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
يُرجى العِلم أنّ الكوروتين لا يزال مطلوبًا هنا، لأنّ makeLoginRequest
دالة suspend
، ويجب تنفيذ جميع دوال suspend
في
كوروتين.
يختلف هذا الرمز عن مثال login
السابق في طريقتان:
- لا يستخدم
launch
مَعلمةDispatchers.IO
. عندما لا تريد تمريرDispatcher
إلىlaunch
، أي كوروتين يتم إطلاقه من يتم تنفيذ "viewModelScope
" في سلسلة المحادثات الرئيسية. - تتم الآن معالجة نتيجة طلب الشبكة لعرض البيانات الناجحة أو إخفاق واجهة المستخدم.
يتم تنفيذ وظيفة تسجيل الدخول الآن على النحو التالي:
- يستدعي التطبيق الدالة
login()
من طبقةView
في سلسلة المحادثات الرئيسية. - ينشئ
launch
كوروتينًا جديدًا في سلسلة التعليمات الرئيسية، ويبدأ الكوروتين. والتنفيذ. - داخل الكوروتين، الاتصال إلى
loginRepository.makeLoginRequest()
يعلّق الآن تنفيذ الكوروتين مرة أخرى حتىwithContext
الحظر فيmakeLoginRequest()
ينتهي قيد التشغيل. - بعد انتهاء حظر
withContext
، سيتم استئناف الكوروتين فيlogin()
التنفيذ في سلسلة التعليمات الرئيسية كنتيجة لطلب الشبكة.
التعامل مع الاستثناءات
لمعالجة الاستثناءات التي يمكن لطبقة Repository
طرحها، استخدِم واجهة برمجة تطبيقات
دعم مضمَّن للاستثناءات.
في المثال التالي، نستخدم كتلة try-catch
:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
في هذا المثال، أيّ استثناء غير متوقّع طرحته السمة makeLoginRequest()
التعامل مع المكالمة على أنها خطأ في واجهة المستخدم.
موارد إضافية حول الكوروتينات
للحصول على نظرة أكثر تفصيلاً على الكوروتينات على Android، يمكنك الاطلاع على تحسين أداء التطبيق باستخدام الكوروتينات في لغة Kotlin
لمزيد من موارد الكوروتينات، اطلع على الروابط التالية:
- نظرة عامة على الكوروتينات (JetBrains)
- دليل الكوروتينات (JetBrains)
- مراجع إضافية حول الكوروتينات في لغة Kotlin وتدفق البيانات