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 ViewModelArchitecture 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:
-
viewModelScopeegy előre definiáltCoroutineScope, amely aViewModelKTX kiterjesztésekkel együtt szerepel. Vegye figyelembe, hogy minden coroutine-nak ascope-ban kell futnia. EgyCoroutineScopeegy vagy több kapcsolódó coroutine-t kezel. -
launchegy 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.IOjelzi, 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
loginfüggvényt aViewrétegből a főszálon. -
launchlé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
loginfü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 makeLoginRequestegy 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:
-
launchnem fogad elDispatchers.IOparamétert. Ha nem adunk át egyDispatcherparamé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 aViewrétegből a főszálon. -
launchlé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őwithContextblokk futása be nem fejeződik. - Amikor a
withContextblokk 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