Android'de Kotlin eş yordamları

Coroutine, Android'de eşzamansız olarak yürütülen kodu basitleştirmek için kullanabileceğiniz bir eşzamanlılık tasarım kalıbıdır. Coroutines 1.3 sürümünde Kotlin'e eklendi ve diğer dillerdeki yerleşik kavramlara dayanıyor.

Android'de eş yordamlar, aksi takdirde ana iş parçacığını engelleyebilecek ve uygulamanızın yanıt vermemesine neden olabilecek uzun süren görevlerin yönetilmesine yardımcı olur. Eş yordamları kullanan profesyonel geliştiricilerin% 50'den fazlası üretkenliklerinin arttığını bildirmiştir. Bu konuda, Kotlin eşyordamlarını kullanarak bu sorunları nasıl giderebileceğiniz ve böylece daha temiz ve kısa uygulama kodu yazabileceğiniz açıklanmaktadır.

Özellikler

Android'de eşzamansız programlama için önerdiğimiz çözüm, Coroutines'dir. Önemli özellikler arasında şunlar yer alır:

  • Hafif: Askıya alma desteği sayesinde tek bir iş parçacığında birçok eş yordam çalıştırabilirsiniz. Bu destek, eş yordamın çalıştığı iş parçacığını engellemez. Askıya alma, birçok eşzamanlı işlemi desteklerken engellemeye kıyasla daha az bellek kullanır.
  • Daha az bellek sızıntısı: Bir kapsamdaki işlemleri çalıştırmak için yapılandırılmış eşzamanlılığı kullanın.
  • Yerleşik iptal desteği: İptal çalışan eşzamanlı rutin hiyerarşisi aracılığıyla otomatik olarak yayılır.
  • Jetpack entegrasyonu: Birçok Jetpack kitaplığı, tam eş yordam desteği sağlayan uzantılar içerir. Bazı kitaplıklar, yapılandırılmış eşzamanlılık için kullanabileceğiniz kendi eş yordam kapsamlarını da sağlar.

Örnekler genel bakış

Uygulama mimarisi kılavuzuna göre, bu konudaki örnekler bir ağ isteğinde bulunur ve sonucu ana iş parçacığına döndürür. Uygulama daha sonra sonucu kullanıcıya gösterebilir.

Daha net bir ifadeyle, ViewModel Mimari bileşen, ağ isteğini tetiklemek için ana iş parçacığındaki depo katmanını çağırır. Bu kılavuzda, ana iş parçacığının engellenmemesi için eş yordamları kullanan çeşitli çözümler açıklanmaktadır.

ViewModel, doğrudan coroutine'lerle çalışan bir dizi KTX uzantısı içerir. Bu uzantılar lifecycle-viewmodel-ktx kitaplığıdır ve bu kılavuzda kullanılır.

Bağımlılık bilgileri

Android projenizde coroutine'leri kullanmak için uygulamanızın build.gradle dosyasına aşağıdaki bağımlılığı ekleyin:

Eski

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

Arka plan iş parçacığında yürütme

Ana iş parçacığında ağ isteğinde bulunmak, yanıt alana kadar ana iş parçacığının beklemesine veya engellenmesine neden olur. İş parçacığı engellendiğinden işletim sistemi onDraw() işlevini çağıramaz. Bu durum, uygulamanızın donmasına ve potansiyel olarak Uygulama Yanıt Vermiyor (ANR) iletişim kutusunun gösterilmesine neden olur. Daha iyi bir kullanıcı deneyimi için bu işlemi arka plan iş parçacığında çalıştıralım.

Öncelikle Repository sınıfımıza göz atıp ağ isteğinin nasıl yapıldığına bakalım:

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

private const val loginUrl = "https://example.com/login"

class LoginRepository(private val responseParser: LoginResponseParser) {
    // 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 eşzamanlıdır ve çağıran iş parçacığını engeller. Ağ isteğinin yanıtını modellemek için kendi Result sınıfımız vardır.

ViewModel, kullanıcı örneğin bir düğmeyi tıkladığında ağ isteğini tetikler:

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody)
    }
}

Önceki kodda, ağ isteği gönderilirken LoginViewModel, kullanıcı arayüzü iş parçacığını engelliyor. Yürütmeyi ana iş parçacığından taşımanın en basit çözümü, yeni bir ortak yordam oluşturup ağ isteğini bir G/Ç iş parçacığında yürütmektir:

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 işlevindeki eş yordam kodunu inceleyelim:

  • viewModelScope, ViewModel KTX uzantılarıyla birlikte gelen önceden tanımlanmış bir CoroutineScope'dir. Tüm eş yordamların bir kapsamda çalışması gerektiğini unutmayın. CoroutineScope, bir veya daha fazla ilgili eşzamanlı işlevi yönetir.
  • launch, bir ortak yordam oluşturan ve işlev gövdesinin yürütülmesini ilgili dağıtıcıya gönderen bir işlevdir.
  • Dispatchers.IO, bu eş yordamın G/Ç işlemleri için ayrılmış bir iş parçacığında yürütülmesi gerektiğini gösterir.

login işlevi şu şekilde yürütülür:

  • Uygulama, ana iş parçacığındaki View katmanından login işlevini çağırıyor.
  • launch yeni bir eş yordam oluşturur ve ağ isteği, G/Ç işlemleri için ayrılmış bir iş parçacığında bağımsız olarak yapılır.
  • Coroutine çalışırken login işlevi yürütülmeye devam eder ve ağ isteği tamamlanmadan önce döndürülür. Basitlik için ağ yanıtının şimdilik yoksayıldığını unutmayın.

Bu eş yordam viewModelScope ile başlatıldığından ViewModel kapsamı içinde yürütülür. Kullanıcı ekrandan uzaklaştığı için ViewModel yok edilirse viewModelScope otomatik olarak iptal edilir ve çalışan tüm eş yordamlar da iptal edilir.

Önceki örnekle ilgili sorunlardan biri, makeLoginRequest işlevini çağıran her şeyin yürütmeyi ana iş parçacığından açıkça taşıması gerektiğidir. Bu sorunu çözmek için Repository değerini nasıl değiştirebileceğimize bakalım.

Ana iş parçacığında güvenli olmak için coroutine'leri kullanın

Bir işlevin, ana iş parçacığında kullanıcı arayüzü güncellemelerini engellemediği durumlarda ana iş parçacığı için güvenli olduğunu kabul ederiz. Ana iş parçacığından makeLoginRequest işlevi çağrıldığında kullanıcı arayüzü engellendiği için makeLoginRequest işlevi ana iş parçacığı için güvenli değildir. Bir eş yordamın yürütülmesini farklı bir iş parçacığına taşımak için eş yordam kitaplığındaki withContext() işlevini kullanın:

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), eş yordamın yürütülmesini bir G/Ç iş parçacığına taşır. Böylece çağırma işlevimiz ana iş parçacığında güvenli hale gelir ve kullanıcı arayüzünün gerektiğinde güncellenmesi sağlanır.

makeLoginRequest, suspend anahtar kelimesiyle de işaretlenir. Bu anahtar kelime, Kotlin'in bir işlevin bir coroutine içinden çağrılmasını zorunlu kılma yöntemidir.

Aşağıdaki örnekte, coroutine LoginViewModel içinde oluşturulur. makeLoginRequest, yürütmeyi ana iş parçacığının dışına taşıdığı için login işlevindeki coroutine artık ana iş parçacığında yürütülebilir:

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 bir suspend işlevi olduğundan ve tüm suspend işlevlerinin bir coroutine içinde yürütülmesi gerektiğinden burada coroutine'in hâlâ gerekli olduğunu unutmayın.

Bu kod, önceki login örneğinden birkaç açıdan farklıdır:

  • launch, Dispatchers.IO parametresini almaz. launch işlevine Dispatcher iletmediğinizde, viewModelScope işlevinden başlatılan tüm eş yordamlar ana iş parçacığında çalışır.
  • Ağ isteğinin sonucu artık başarı veya başarısızlık kullanıcı arayüzünü gösterecek şekilde işleniyor.

Giriş işlevi artık şu şekilde yürütülür:

  • Uygulama, ana iş parçacığındaki View katmanından login() işlevini çağırıyor.
  • launch ana iş parçacığında yeni bir eş yordam oluşturur ve eş yordam yürütülmeye başlar.
  • Coroutine içinde loginRepository.makeLoginRequest() now çağrısı, makeLoginRequest() içindeki withContext bloğu çalışmayı bitirene kadar coroutine'in yürütülmesini askıya alır.
  • withContext bloğu tamamlandığında login() içindeki coroutine, ağ isteğinin sonucuyla ana iş parçacığında yürütülmeye devam eder.

İstisnaları işleme

Repository katmanının oluşturabileceği istisnaları işlemek için Kotlin'in istisnalar için yerleşik desteğini kullanın. Aşağıdaki örnekte bir try-catch bloğu kullanıyoruz:

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 */ }
            }
        }
    }
}

Bu örnekte, makeLoginRequest() çağrısı tarafından oluşturulan beklenmedik istisnalar kullanıcı arayüzünde hata olarak ele alınır.

Ek eş yordam kaynakları

Android'de eş yordamlar hakkında daha ayrıntılı bilgi için Kotlin eş yordamlarıyla uygulama performansını artırma başlıklı makaleyi inceleyin.

Daha fazla coroutine kaynağı için aşağıdaki bağlantılara bakın: