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ış birCoroutineScope
'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ığındakiView
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 birDispatcher
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ığındakiView
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ıkmakeLoginRequest()
içindekiwithContext
blokunun çalışması bitene kadar eş yordasının başka bir yürütülmesini askıya alır. withContext
bloku bittikten sonralogin()
ü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: