الكوروتينات في لغة Kotlin على Android (original) (raw)

الكوروتين هو نمط تصميم متزامن يمكنك استخدامه على تبسيط الرمز الذي يتم تنفيذه بشكل غير متزامن في Androidالكوروتينإلى Kotlin في الإصدار 1.3 وتستند إلى المفاهيم من اللغات الأخرى.

على نظام Android، يساعد الكوروتين في إدارة المهام الطويلة المدى التي قد حظر سلسلة التعليمات الرئيسية وجعل التطبيق لا يستجيب. أفاد أكثر من 50% من المطوّرين المحترفين الذين يستخدمون الكوروتينات بأنهم زيادة الإنتاجية. يصف هذا الموضوع كيفية استخدام الكوروتينات في لغة Kotlin لمعالجة هذه مما يتيح لك كتابة كود تطبيق أكثر وضوحًا وإيجازًا.

الميزات

الكوروتينات هو الحل الذي ننصح به للبرمجة غير المتزامنة على Android وتشمل الميزات البارزة ما يلي:

نظرة عامة على الأمثلة

استنادًا إلى دليل بنية التطبيق، يمكن العثور على في هذا الموضوع، أرسل طلبًا عبر الشبكة وارجع النتيجة إلى حيث يمكن للتطبيق عرض النتيجة للمستخدم.

وعلى وجه التحديد، 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:

يتم تنفيذ الدالة 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 السابق في طريقتان:

يتم تنفيذ وظيفة تسجيل الدخول الآن على النحو التالي:

التعامل مع الاستثناءات

لمعالجة الاستثناءات التي يمكن لطبقة 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

لمزيد من موارد الكوروتينات، اطلع على الروابط التالية: