الكوروتين هو نمط تصميم متزامن يمكنك استخدامه على تبسيط الرمز الذي يتم تنفيذه بشكل غير متزامن في 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
لمزيد من موارد الكوروتينات، اطلع على الروابط التالية: