Android'de Kotlin eş yordamları

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

Android'de eş yordamlar, aksi halde ana iş parçacığını engelleyebilecek ve uygulamanızın yanıt vermemesine neden olabilecek uzun süreli görevlerin yönetilmesine yardımcı olur. Eş yordam kullanan profesyonel geliştiricilerin% 50'sinden fazlası üretkenlikte artış olduğunu bildirmiştir. Bu bölümde, bu sorunları gidermek için Kotlin eş yordamlarını nasıl kullanabileceğiniz açıklanmaktadır. Böylece, daha net ve öz uygulama kodları yazabilirsiniz.

Özellikler

Coroutines, Android'de eşzamansız programlama için önerdiğimiz çözümdür. Dikkate değer özellikler arasında şunlar sayılabilir:

  • Hafiftir: Eşliğin çalıştığı iş parçacığını engellemeyen askıya alma desteği nedeniyle tek bir iş parçacığında çok sayıda eş yordam çalıştırabilirsiniz. Askıya alma işlemi, birçok eşzamanlı işlemi desteklerken engelleme karşısında bellekten tasarruf sağlar.
  • Daha az bellek sızıntısı: Belirli bir kapsam dahilindeki işlemleri çalıştırmak için yapılandırılmış eşzamanlılık kullanın.
  • Yerleşik iptal desteği: İptal işlemi, çalışan eş yordam hiyerarşisinde otomatik olarak aktarı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şleşen kapsamlarını da sağlar.

Örneklere genel bakış

Uygulama mimarisi kılavuzu'na bağlı olarak, bu konudaki örnekler bir ağ isteğinde bulunur ve sonucu ana iş parçacığına döndürür. Uygulama, burada sonucu kullanıcıya gösterebilir.

ViewModel Mimari bileşeni, 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 engelini kaldırmak için eş yordamlar kullanan çeşitli çözümler yinelenir.

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

Bağımlılık bilgileri

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

Groovy

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 bir ağ isteği yapılması, yanıt alana kadar beklemesine veya engellenmesine neden olur. İş parçacığı engellendiği için işletim sistemi onDraw()'i çağıramaz. Bu durum, uygulamanızın donmasına ve muhtemelen Uygulama Yanıt Vermiyor (ANR) iletişim kutusuna yol açar. Daha iyi bir kullanıcı deneyimi için bu işlemi bir arka plan iş parçacığı üzerinde çalıştıralım.

Önce Repository sınıfımıza göz atalım ve ağ isteğini nasıl yerine getirdiğini görelim:

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 eşzamanlıdır ve çağrı yapan ileti dizisini engeller. Ağ isteği yanıtını modellemek için kendi Result sınıfımız vardır.

ViewModel, kullanıcı 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 LoginViewModel, ağ isteğinde bulunurken kullanıcı arayüzü iş parçacığını engeller. Yürütmeyi ana iş parçacığının dışına taşımanın en basit çözümü, yeni bir eş yordam oluşturmak ve 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 fonksiyonundaki eş yordam kodunu ayrıştıralım:

  • viewModelScope, ViewModel KTX uzantılarına dahil edilen önceden tanımlanmış bir CoroutineScope değeridir. Tüm eş yordamların bir kapsam içinde çalışması gerektiğini unutmayın. CoroutineScope, ilgili bir veya daha fazla eş çocuğu yönetir.
  • launch, eş yordam oluşturan ve işlev gövdesinin yürütülmesini ilgili görev dağıtıcıya gönderen bir işlevdir.
  • Dispatchers.IO, bu eş yordasının G/Ç işlemleri için ayrılmış bir iş parçacığında yürütülmesi gerektiğini belirtir.

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

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

Bu eş yordam viewModelScope ile başlatıldığından ViewModel kapsamında yürütülür. ViewModel, kullanıcı ekrandan ayrıldığı için kaldırılırsa viewModelScope otomatik olarak iptal edilir ve çalışan tüm eşler de iptal edilir.

Önceki örnekle ilgili sorunlardan biri, makeLoginRequest çağrısı yapan her şeyin yürütmeyi ana iş parçacığının dışına taşımasını açıkça unutmasının gerekmesidir. Bu sorunu bizim için çözmek üzere Repository üzerinde nasıl değişiklik yapabileceğimize bakalım.

Ana güvenlik için eş yordamlar kullan

Ana iş parçacığındaki kullanıcı arayüzü güncellemelerini engellemeyen bir işlevi main-güvenli olarak kabul ederiz. Ana iş parçacığından makeLoginRequest çağrısı yapmak kullanıcı arayüzünü engelleyeceğinden makeLoginRequest işlevi main güvenli değildir. Bir eş çocuğun 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ş kotinin yürütülmesini bir I/O iş parçacığına taşıyarak çağrı işlevimizi "ana güvenli" hale getirir ve kullanıcı arayüzünün gerektiğinde güncellenmesini sağlar.

makeLoginRequest etiketi de suspend anahtar kelimesiyle işaretlenmiş. Bu anahtar kelime, Kotlin'in bir eş yordam içinden çağrılacak bir işlevi zorunlu kılma yöntemidir.

Aşağıdaki örnekte, eş yordam LoginViewModel içinde oluşturulmuştur. makeLoginRequest yürütmeyi ana iş parçacığının dışına taşırken login işlevindeki eş yordam 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 eş yordamda yürütülmesi gerektiğinden eş yordanın burada hâlâ gerekli olduğunu unutmayın.

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

  • launch, Dispatchers.IO parametresi almaz. launch hedefine bir Dispatcher iletmediğinizde viewModelScope öğesinden başlatılan tüm eş yordamlar ana iş parçacığında çalışır.
  • Ağ isteğinin sonucu, başarılı veya başarısız kullanıcı arayüzünü görüntülemek için artık işlenmektedir.

Giriş işlevi artık şu şekilde çalışmaktadır:

  • Uygulama, ana iş parçacığındaki View katmanından login() işlevini çağırır.
  • launch, ana iş parçacığında yeni bir eş yordam oluşturur ve eş yordam yürütmeye başlar.
  • Eşzamanlında, loginRepository.makeLoginRequest() çağrısı, makeLoginRequest() içindeki withContext bloğunun çalışması bitene kadar eş yordasının daha fazla yürütülmesini askıya alır.
  • withContext bloğu sona erdiğinde login() içindeki eş yordam, ağ isteğinin sonucuyla ana iş parçacığında yürütmeye devam eder.

İstisnaları işleme

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

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ınmaktadır.

Ek eş yordam kaynakları

Android'de eş yordamlara daha ayrıntılı bir bakış için Kotlin eş yordamlarıyla uygulama performansını iyileştirme bölümüne bakın.

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