Android'de Kotlin eş yordamları

Eşzamanlılık, eşzamansız olarak çalışan kodları 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ı çözmek ve daha anlaşılır ve özlü uygulama kodları yazabilmenizi sağlamak için Kotlin eş yordamlarını nasıl kullanabileceğiniz açıklanmaktadır.

Özellikler

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

  • Hafif: Ortakliğin çalıştığı iş parçacığını engellemeyen askıya alma desteği sayesinde 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 yerine bellek tasarrufu 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şisi aracılığıyla otomatik olarak dağıtılır.
  • Jetpack entegrasyonu: Çoğu 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 rehberine 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ılavuz, ana iş parçacığının engelini ortadan kaldıran eş yordaların kullanıldığı çeşitli çözümlerle tekrarlanır.

ViewModel, doğrudan eş yordamlarla çalışan bir dizi KTX uzantısı içerir. lifecycle-viewmodel-ktx kitaplığı olan bu uzantılar 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:

Modern

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() kodunu çağıramaz. Bu durum, uygulamanızın donmasına neden olur ve potansiyel olarak Uygulama Yanıt Vermiyor (ANR) iletişim kutusuna yol açabilir. Daha iyi bir kullanıcı deneyimi için bu işlemi bir arka plan iş parçacığı üzerinde çalıştıralım.

İlk olarak Repository sınıfımıza göz atalım ve ağ isteğini nasıl yaptığını 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ı ve çağrıda bulunan mesaj dizisini engelliyor. Ağ isteğinin yanıtına yönelik modelleme yapmak 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 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 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ına dahil edilen, önceden tanımlanmış bir CoroutineScope'dır. Tüm eş yordamların bir kapsam içinde çalışması gerektiğini unutmayın. CoroutineScope, ilgili bir veya daha fazla eş yordamı yönetir.
  • launch, bir 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ş yordamın G/Ç işlemleri için ayrılmış bir iş parçacığında yürütülmesi gerektiğini belirtir.

login işlevi aşağıdaki gibi yürütülür:

  • Uygulama, login işlevini ana iş parçacığındaki View katmanından çağırır.
  • 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.
  • 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ş yordalar da 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şımayı hatırlamasının gerekmesidir. Şimdi, 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ığında kullanıcı arayüzü güncellemelerini engellemeyen bir işlevin ana güvenli olduğu kabul edilir. Ana iş parçacığından makeLoginRequest çağrılması kullanıcı arayüzünü engellediğinden makeLoginRequest işlevi main güvenli değil. Bir eş yordasını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 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ği gibi güncellenmesini sağlar.

makeLoginRequest ayrıca suspend anahtar kelimesiyle işaretlenmiştir. 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ığı 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ş yordam içinde yürütülmesi gerektiğinden burada eş yordamın hâlâ gerekli olduğunu unutmayın.

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

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

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

  • Uygulama, login() işlevini ana iş parçacığındaki View katmanından ç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ı artık makeLoginRequest() içindeki withContext blokunun çalışması bitene kadar eş yordasının başka bir yürütülmesini askıya alır.
  • withContext bloku bittikten sonra login() üzerindeki eş yordam, ağ isteğinin sonucuyla birlikte ana iş parçacığında yürütmeye devam eder.

İstisnaları işleme

Repository katmanının oluşturabileceği istisnaları ele almak 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ıyla oluşturulan beklenmedik istisnalar kullanıcı arayüzünde hata olarak ele alınmaktadır.

Ek eş yordam kaynakları

Android'deki eş yordamlarla ilgili daha ayrıntılı bilgi 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: