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:

  • viewModelScope egy előre definiált CoroutineScope, amely a ViewModel KTX kiterjesztésekkel együtt szerepel. Vegye figyelembe, hogy minden coroutine-nak ascope-ban kell futnia. Egy CoroutineScope 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 a View 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 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:

  • launch nem fogad el Dispatchers.IO paramétert. Ha nem adunk át egy Dispatcher paramétert a launch-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 a View 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 a makeLoginRequest()-ben lévő withContextblokk futása be nem fejeződik.
  • Amikor a withContext blokk befejeződik, a login()-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