Kotlin coroutine-ok Androidon
A coroutine egy párhuzamos tervezési minta, amelyet Androidon használhatsz az aszinkron módon végrehajtott kód egyszerűsítésére.A coroutine-ok az 1.3-as verzióban kerültek hozzá a Kotlinhoz, és más nyelvek bevált koncepcióin alapulnak.
Az Androidon a coroutine-ok segítenek kezelni a hosszú futású feladatokat, amelyek máskülönben blokkolnák a főszálat, és az alkalmazás nem reagálna.A coroutine-okat használó professzionális fejlesztők több mint 50%-a számolt be a termelékenység növekedéséről.Ez a téma leírja, hogyan használhatja a Kotlin coroutine-okat e problémák megoldására, lehetővé téve, hogy tisztább és tömörebb alkalmazáskódot írjon.
Tulajdonságok
A coroutines az ajánlott megoldásunk az aszinkron programozásra azAndroidon. A figyelemre méltó jellemzők közé tartoznak a következők:
- Könnyűsúlyú: Sok coroutine futtatható egyetlen szálon, mivel támogatja a felfüggesztést, ami nem blokkolja azt a szálat, ahol a coroutine fut. A felfüggesztés memóriát takarít meg a blokkolással szemben, miközben sok egyidejű műveletet támogat.
- Kevesebb memóriaszivárgás: Használja a strukturált párhuzamosságot a műveletek futtatásához egy hatókörön belül.
- Beépített törlési támogatás: A törlés automatikusan terjed a futó coroutine hierarchián keresztül.
- Jetpack integráció: Sok Jetpack könyvtár tartalmaz olyan bővítményeket, amelyek teljes coroutine támogatást nyújtanak. Egyeskönyvtárak sajátcoroutine hatókörüket is biztosítják, amelyeket strukturált párhuzamossághoz használhat.
Példák áttekintése
A Guide to app architecture alapján az ebben a témakörben szereplő példák hálózati kérést végeznek, és az eredményt visszaadják a főszálnak, ahol az alkalmazás ezután megjelenítheti az eredményt a felhasználónak.
Kifejezetten az ViewModel
Architecture komponens a főszálon hívja meg a repository réteget a hálózati kérés elindításához. Ez az útmutató különböző megoldásokat ismétel végig, amelyek coroutine-okat használnak, hogy a főszálat ne blokkolják.
ViewModel
tartalmaz egy sor KTX-bővítményt, amelyek közvetlenül a coroutine-okkal dolgoznak. Ezek a kiterjesztéseklifecycle-viewmodel-ktx
könyvtárak, és ebben az útmutatóban használjuk őket.
Függőségi információ
A coroutine-ok használatához az Android projektben a következő függőséget kell hozzáadni az alkalmazás build.gradle
fájljához:
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}
Háttérszálban történő végrehajtás
Hálózati kérés végrehajtása a főszálon azt eredményezi, hogy az várakozik, vagy blokkol,amíg választ nem kap. Mivel a szál blokkolva van, az operációs rendszer meghívja a onDraw()
-t, ami az alkalmazás lefagyását okozza, és potenciálisan egy Application Not Responding (ANR) párbeszédpanelt eredményez. A jobb felhasználói élmény érdekében futtassuk ezt a műveletet egy háttérszálon.
Először is nézzük meg a Repository
osztályunkat, és nézzük meg, hogyan teljesíti a hálózati kérést:
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
szinkron és blokkolja a hívó szálat. A hálózati kérés válaszának modellezésére saját Result
osztályunk van.
A ViewModel
kiváltja a hálózati kérést, amikor a felhasználó például egy gombra kattint:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}
Az előző kóddal a LoginViewModel
blokkolja a felhasználói felület szálát, amikor a hálózati kérést teszi. A legegyszerűbb megoldás a végrehajtás áthelyezésére a főszálról, ha létrehozunk egy új coroutine-t, és a hálózati kérést egy I/O szálon hajtjuk végre:
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) } }}
Vizsgáljuk meg a coroutine kódját a login
függvényben:
-
viewModelScope
egy előre definiáltCoroutineScope
, amely aViewModel
KTX kiterjesztésekkel együtt szerepel. Vegye figyelembe, hogy minden coroutine-nak ascope-ban kell futnia. EgyCoroutineScope
egy vagy több kapcsolódó coroutine-t kezel. -
launch
egy olyan függvény, amely létrehoz egy coroutine-t és a függvénytest végrehajtását a megfelelő diszpécserhez küldi. -
Dispatchers.IO
jelzi, hogy ezt a coroutine-t az I/O műveletekre fenntartott athread-en kell végrehajtani.
A login
függvény végrehajtása a következőképpen történik:
- Az alkalmazás meghívja a
login
függvényt aView
rétegből a főszálon. -
launch
létrehoz egy új coroutine-t, és a hálózati kérés önállóan történik egy I/O műveletekre fenntartott szálon. - Mialatt a coroutine fut, a
login
függvény folytatja a végrehajtástés visszatér, esetleg a hálózati kérés befejezése előtt. Megjegyezzük, hogy az egyszerűség kedvéért a hálózati választ egyelőre figyelmen kívül hagyjuk.
Mivel ez a coroutine a viewModelScope
-mal indult, a ViewModel
hatókörében kerül végrehajtásra. Ha a ViewModel
megsemmisül, mert a felhasználó elnavigál a képernyőtől, a viewModelScope
automatikusan törlődik, és az összes futó coroutine is törlődik.
Az előző példával kapcsolatban az egyik probléma az, hogy bármi, ami a makeLoginRequest
-t hívja, nem szabad elfelejtenie, hogy a végrehajtást explicit módon áthelyezze a főszálról. Lássuk, hogyan módosíthatjuk a Repository
-t, hogy megoldja számunkra ezt a problémát.
Korutinok használata a main-safe
Egy függvényt main-safe-nek tekintünk, ha nem blokkolja az UI frissítéseket a main thread-en. A makeLoginRequest
függvény nem main-safe, mivel amakeLoginRequest
hívása a főszálból blokkolja a felhasználói felületet. Használjuk awithContext()
függvényt a coroutines könyvtárból, hogy egy coroutine végrehajtását egy másik szálra helyezzük át:
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)
a coroutine végrehajtását egyI/O szálra helyezi át, így a hívó függvényünk main-safe lesz, és szükség szerint lehetővé teszi az UI frissítését.
makeLoginRequest
szintén a suspend
kulcsszóval van jelölve. Ez a kulcsszóa Kotlin módszere arra, hogy kikényszerítse egy függvény meghívását egy coroutine-on belülről.
A következő példában a coroutine a LoginViewModel
-ben jön létre.Mivel a makeLoginRequest
áthelyezi a végrehajtást a főszálról, a login
függvényben lévő coroutine most már a főszálban hajtható végre:
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 } } }}
Megjegyezzük, hogy a coroutine-ra itt is szükség van, mivel a makeLoginRequest
egy suspend
függvény, és minden suspend
függvényt coroutine-ban kell végrehajtani.
Ez a kód néhány dologban különbözik az előző login
példától:
-
launch
nem fogad elDispatchers.IO
paramétert. Ha nem adunk át egyDispatcher
paramétert alaunch
-nak, akkor aviewModelScope
-ból indított coroutine-ok a főszálban futnak. - A hálózati kérés eredményét most úgy kezeljük, hogy megjelenik az utód hiba UI.
A bejelentkezési függvény most a következőképpen hajtódik végre:
- Az alkalmazás meghívja a
login()
függvényt aView
rétegből a főszálon. -
launch
létrehoz egy új coroutine-t a hálózati kérés végrehajtásához a főszálon, és a coroutine megkezdi a végrehajtást. - A coroutine-on belül a
loginRepository.makeLoginRequest()
now hívása felfüggeszti a coroutine további végrehajtását, amíg amakeLoginRequest()
-ben lévőwithContext
blokk futása be nem fejeződik. - Amikor a
withContext
blokk befejeződik, alogin()
-ban lévő coroutine folytatja a végrehajtást a főszálon a hálózati kérés eredményével.
Kivételek kezelése
Az Repository
réteg által dobható kivételek kezeléséhez használjuk a Kotlin beépített támogatását a kivételekre.A következő példában egy try-catch
blokkot használunk:
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 } } }}
Ebben a példában a makeLoginRequest()
hívás által dobott váratlan kivételeket hibaként kezeli a felhasználói felület.
Leave a Reply