Zaman aşımı, eşzamansız olarak çalışan kodu basitleştirmek için Android'de kullanabileceğiniz bir eşzamanlılık tasarım modelidir. Korintinler, Kotlin'in 1.3 sürümünde eklenmiş olup diğer dillerde belirlenmiş kavramları temel almaktadır.
Android'de, eş zamanlı olarak ana iş parçacığını engelleyip uygulamanızın yanıt vermemesine neden olabilecek uzun süreli görevlerin yönetilmesine yardımcı olabilirsiniz. Korintin kullanan profesyonel geliştiricilerin% 50'sinden fazlası üretkenliği artırdığını bildirmiştir. Bu makalede, bu sorunları gidermek için Kotlin eş yordamlarını nasıl kullanabileceğiniz açıklanmakta, böylece daha net ve özlü bir uygulama kodu yazabilmektedir.
Özellikler
Coroutines, Android'de eşzamansız programlama için önerilen çözümümüzdür. Önemli özellikler arasında aşağıdakiler bulunur:
- Hafiftir: Askıya alma desteği nedeniyle tek bir ileti dizisinde çok sayıda eş yordam çalıştırabilirsiniz. koordinasyonun çalıştığı mesaj dizisini engellemeyin. Askıya alma, aynı anda birçok işlemi desteklerken engelleme yerine bellekten tasarruf eder.
- Daha az bellek sızıntısı: İşlemleri bir kapsamda çalıştırmak için yapılandırılmış eşzamanlılığı kullanın.
- Yerleşik iptal desteği: İptal işlemi, çalışan eş yordam hiyerarşisinde otomatik olarak uygulanır.
- Jetpack entegrasyonu: Jetpack kitaplıklarının çoğu, eş yordamlar için tam destek sağlayan uzantılar içerir. Bazı kitaplıklar, yapılandırılmış eşzamanlılık için kullanabileceğiniz kendi bağdaştırıcı kapsamını da sağlar.
Örneklere 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. Burada uygulama, sonucu kullanıcıya gösterebilir seçeneğini tıklayın.
ViewModel
Mimari bileşeni, ağ isteğini tetiklemek için ana iş parçacığındaki depo katmanını çağırır. Bu kılavuz, koronine kullanılan çeşitli çözümler aracılığıyla tekrarlayarak ana iş parçacığının engellemesini kaldırmaktadır.
ViewModel
, doğrudan korintlerle çalışan bir dizi KTX uzantısı içerir. Bu uzantılar lifecycle-viewmodel-ktx
kitaplığıdır ve bu rehberde kullanılı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ı özelliğini ekleyin:
Eğlenceli
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Arka plandaki ileti dizisinde yürütme
Ana iş parçacığında ağ isteğinde bulunmak, yanıt alana kadar ağın beklemesine veya engellenmesine neden olur. Mesaj dizisi engellendiğinden işletim sistemi onDraw()
işlevini çağıramaz. Bu durum, uygulamanızın donmasına neden olabilir ve bir Uygulama Yanıt Vermiyor (ANR) iletişim kutusuna yol açabilir. Daha iyi bir kullanıcı deneyimi için bu işlemi arka plandaki bir mesaj dizisinde yapalım.
İlk olarak, Repository
dersimize bir göz atalım ve bunun 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 arama dizisini engeller. Ağ isteğinin yanıtını modellemek için kendi Result
sınıfımız var.
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 kodla, LoginViewModel
ağ isteğinde bulunurken kullanıcı arayüzü iş parçacığını engelliyor. 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 bir I/O ileti dizisinde ağ isteğini 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ş yordamlar kodunu inceleyelim:
viewModelScope
,ViewModel
KTX uzantısına dahil olan önceden tanımlanmış birCoroutineScope
'dir. Tüm koroninin bir kapsamda çalışması gerektiğini unutmayın.CoroutineScope
, bir veya daha fazla alakalı eş yordamı yönetir.launch
bir eş yordam oluşturan ve işlevin yürütülmesini ilgili yetkiliye gönderen bir işlevdir.Dispatchers.IO
, bu eş adresin I/O işlemleri için ayrılmış bir ileti dizisinde yürütülmesi gerektiğini belirtir.
login
işlevi aşağıdaki gibi yürütülür:
- Uygulama, ana mesaj dizisindeki
View
katmanındanlogin
işlevini çağırır. launch
yeni bir eş yordam oluşturur ve ağ isteği, G/Ç işlemleri için ayrılmış bir ileti dizisinden bağımsız olarak yapılır.- Eşzamanlı çalışırken,
login
işlevi yürütme işlemine devam eder ve ağ isteği tamamlanmadan önce geri döner. Kolaylık açısından, ağ yanıtının şimdilik yoksayıldığını unutmayın.
Bu coroutine viewModelScope
ile başladığından ViewModel
kapsamında yürütülür. Kullanıcı ekrandan ayrıldığı için ViewModel
kaldırılırsa viewModelScope
otomatik olarak iptal edilir ve çalışan tüm koordinatlar da iptal edilir.
Önceki örnekle ilgili bir sorun, makeLoginRequest
işlevini çağıran her şeyin, yürütmeyi ana iş parçacığının dışına taşımayı unutmamasıdır. Ş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ın
Ana iş parçacığında kullanıcı arayüzü güncellemelerini engellemeyen bir işlevi ana güvenli olarak kabul ederiz. Ana işlevden makeLoginRequest
işlevinin çağrılması kullanıcı arayüzünü engellediğinden makeLoginRequest
işlevi ana açısından güvenli değildir. Coroutine'in yürütme işlemini farklı bir ileti dizisine taşımak için coroutines 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)
, kordonun yürütülmesini bir G/Ç ileti dizisine taşır. Böylece, arama işlevimiz güvenli bir hale gelir ve kullanıcı arayüzünün gereken şekilde güncellenmesi sağlanır.
makeLoginRequest
ayrıca suspend
anahtar kelimesiyle de işaretlendi. Bu anahtar kelime, Kotlin tarafından bir kordonun içinden çağrılacak bir işlevi zorunlu kılmanın bir yoludur.
Aşağıdaki örnekte LoginViewModel
biçiminde coroutine 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 korintte yürütülmesi gerektiğinden buradaki korintin hâlâ gerekli olduğunu unutmayın.
Bu kod, önceki login
örneğinden iki açıdan farklıdır:
launch
,Dispatchers.IO
parametresini kabul etmez.launch
işlevine birDispatcher
değerini iletmediğinizdeviewModelScope
kaynağından başlatılan eş zamanlı reklamlar ana ileti dizisinde çalıştırılır.- Ağ isteğinin sonucu artık başarılı veya başarısız kullanıcı arayüzünü göstermek için işleniyor.
Giriş işlevi artık şu şekilde çalışıyor:
- Uygulama, ana mesaj dizisindeki
View
katmanındanlogin()
işlevini çağırır. launch
, ana iş parçacığında yeni bir eş yordam oluşturur ve korint, yürütmeye başlar.loginRepository.makeLoginRequest()
politikasında yapılan çağrı, şimdimakeLoginRequest()
içindewithContext
blokunun çalışması bitene kadar kordonin daha fazla yürütülmesini askıya alır.withContext
engellemesi tamamlandıktan sonralogin()
uygulamasındaki biçim, ağ isteğinin sonucu olarak ana iş parçacığında yürütmeyi devam ettirir.
İstisnaları işleme
Repository
katmanının uygulayabileceği istisnaları yönetmek için Kotlin'in istisnalar için yerleşik desteğini kullanın.
Aşağıdaki örnekte try-catch
bloğu kullanılmıştır:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun makeLoginRequest(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ının attığı beklenmedik istisnalar kullanıcı arayüzünde hata olarak ele alınır.
Ek coroutines kaynakları
Android'de eş yordamlar hakkında daha ayrıntılı bilgi için Kotlin coroutines ile uygulama performansını iyileştirme bölümüne bakın.
Eş yordamlarıyla ilgili daha fazla kaynak için aşağıdaki bağlantıları inceleyin:
- Coroutines'e genel bakış (JetBrains)
- Coroutines rehberi (JetBrains)
- Kotlin eş yordamları ve akışı için ek kaynaklar