Kotlin-koroutiinit Androidissa
Koroutiini on rinnakkaissuunnittelumalli, jota voit käyttää Androidissa yksinkertaistaaksesi koodia, joka suoritetaan asynkronisesti.Koroutiinit lisättiin Kotliniin versiossa 1.3, ja ne perustuvat muiden kielten vakiintuneisiin konsepteihin.
Androidissa koroutiineja käytetään hallitsemaan pitkäkestoisia tehtäviä, jotka muuten saattaisivat blokata pääsäikeen ja aiheuttaa sovelluksen reagoimattomuuden.Yli 50 % koroutiineja käyttävistä ammattikehittäjistä on raportoinut tuottavuuden kasvaneen.Tässä aiheessa kuvataan, miten voit käyttää Kotlinin koroutiineja näiden ongelmien ratkaisemiseen, jolloin voit kirjoittaa siistimpää ja tiiviimpää sovelluskoodia.
Ominaisuudet
Koroutiinit on suositeltu ratkaisumme asynkroniseen ohjelmointiin Androidissa. Huomionarvoisia ominaisuuksia ovat seuraavat:
- Kevyt: Voit suorittaa useita korutiineja yhdessä säikeessä, koska se tukee keskeytystä, joka ei estä säiettä, jossa korutiini on käynnissä. Keskeyttäminen säästää muistia estämiseen verrattuna ja tukee samalla monia samanaikaisia toimintoja.
- Vähemmän muistivuotoja: Käyttörakenteinen samanaikaisuus operaatioiden suorittamiseen laajuuden sisällä.
- Sisäänrakennettu peruutustuki: Peruutus leviää automaattisesti käynnissä olevan korutiinin hierarkian läpi.
- Jetpack-integraatio: Monet Jetpack-kirjastot sisältävätlaajennuksia, jotka tarjoavat täyden koroutiinituen. Jotkin kirjastot tarjoavat myös omancoroutine-aluekokonaisuutensa, jota voit käyttää strukturoituun rinnakkaisuuteen.
Yleiskatsaus esimerkkeihin
Sovellusarkkitehtuurin oppaan perusteella tämän aiheen esimerkit tekevät verkkopyynnön ja palauttavat tuloksen pääsäikeeseen, jossa sovellus voi sitten näyttää tuloksen käyttäjälle.
Kohtaisesti ViewModel
Arkkitehtuurikomponentti kutsuu pääsäikeessä olevaa arkistokerroksen kerrosta verkkopyynnön laukaisemiseksi. Tässä oppaassa käydään läpi erilaisia ratkaisuja, jotka käyttävät koroutiineja pitääkseen pääsäikeen vapaana.
ViewModel
sisältää joukon KTX-laajennuksia, jotka toimivat suoraan koroutiineilla. Nämä laajennukset ovatlifecycle-viewmodel-ktx
kirjasto ja niitä käytetään tässä oppaassa.
Tietoa riippuvuudesta
Käyttääksesi korutiineja Android-projektissasi, lisää seuraava riippuvuus sovelluksesi build.gradle
tiedostoon:
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}
Toteutus taustasäikeessä
Verkkoyhteyspyynnön tekeminen pääsäikeessä saa sen odottamaan eli blokkaamaan vastauksen saamiseen asti. Koska säie on estynyt, käyttöjärjestelmä ei voi kutsua onDraw()
, mikä aiheuttaa sovelluksen jähmettymisen ja mahdollisesti johtaa Application Not Responding (ANR) -valintaikkunaan. Paremman käyttökokemuksen saamiseksi suoritetaan tämä toiminto taustasäikeessä.
Katsotaan ensin Repository
-luokkaamme ja katsotaan, miten se suorittaa verkkopyynnön:
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
on synkroninen ja estää kutsuvan säikeen. Verkkopyynnön vastauksen mallintamiseen meillä on oma Result
-luokkamme.
ViewModel
laukaisee verkkopyynnön, kun käyttäjä napsauttaa esimerkiksi painiketta:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}
Edellisellä koodilla LoginViewModel
estää UI-säiettä tehdessään verkkopyynnön. Yksinkertaisin ratkaisu suorituksen siirtämiseksi pois pääsäikeestä on luoda uusi coroutine ja suorittaa verkkopyyntö I/O-säikeessä:
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) } }}
Paloitellaanpa coroutinen koodia login
-funktiossa:
-
viewModelScope
on valmiiksi määriteltyCoroutineScope
, joka sisältyyViewModel
KTX-laajennuksiin. Huomaa, että kaikki coroutines on suoritettava ascope.CoroutineScope
hallinnoi yhtä tai useampaa toisiinsa liittyvää korutiinia. -
launch
on funktio, joka luo korutiinin ja lähettää sen funktiorungon suorituksen vastaavalle dispatcherille. -
Dispatchers.IO
ilmaisee, että tämä korutiini on suoritettava I/O-operaatioita varten varatussa säikeessä.
Funktio login
suoritetaan seuraavasti:
- Sovellus kutsuu
login
-funktiotaView
-kerroksesta pääsäikeessä. -
launch
luo uuden coroutinin, ja verkkopyyntö tehdään itsenäisesti I/O-operaatioita varten varatussa säikeessä. - Korutiinin suorituksen aikana
login
-funktio jatkaa suoritustaja palaa, mahdollisesti ennen kuin verkkopyyntö on valmis. Huomaa, että yksinkertaisuuden vuoksi verkkovastaus jätetään toistaiseksi huomiotta.
Koska tämä coroutiini on käynnistetty viewModelScope
:lla, se suoritetaan ViewModel
:n vaikutusalueella. Jos ViewModel
tuhoutuu, koska käyttäjä siirtyy pois näytöltä, viewModelScope
peruuntuu automaattisesti, ja kaikki käynnissä olevat coroutiinit peruuntuvat myös.
Yksi ongelma edellisessä esimerkissä on se, että kaiken, mikä kutsuu makeLoginRequest
:ää, on muistettava siirtää suoritus eksplisiittisesti pois pääsäikeestä. Katsotaanpa, miten voimme muuttaa Repository
ratkaisemaan tämän ongelman puolestamme.
Käytä korutiineja pääturvallisuuteen
Me pidämme funktiota pääturvallisena, kun se ei estä käyttöliittymän päivityksiä pääsäikeessä. Funktio makeLoginRequest
ei ole pääturvallinen, koskamakeLoginRequest
:n kutsuminen pääsäikeestä estää käyttöliittymän. KäytäwithContext()
-funktiota coroutines-kirjastosta siirtääksesi coroutinen suorituksen toiseen säikeeseen:
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)
siirtää coroutinen suorituksenI/O-säikeeseen, mikä tekee kutsuvasta funktiostamme main-safe ja mahdollistaa UI:n päivittämisen tarvittaessa.
makeLoginRequest
on myös merkitty avainsanalla suspend
. Tämä avainsanaon Kotlinin tapa pakottaa funktio kutsuttavaksi coroutinen sisältä.
Seuraavassa esimerkissä coroutine on luotu LoginViewModel
.Koska makeLoginRequest
siirtää suorituksen pois pääsäikeestä, coroutinefunktiossa login
voidaan nyt suorittaa pääsäikeessä:
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 } } }}
Huomaa, että coroutinea tarvitaan tässäkin, koska makeLoginRequest
on suspend
-funktio, ja kaikki suspend
-funktiot on suoritettava coroutinessa.
Tämä koodi eroaa edellisestä login
esimerkistä muutamalla tavalla:
-
launch
ei otaDispatchers.IO
parametria. KunDispatcher
ei välitälaunch
:lleDispatcher
, kaikkiviewModelScope
:sta käynnistetyt korutiinit suoritetaan pääsäikeessä. - Verkkopyynnön tulosta käsitellään nyt siten, että näytetään seuraajan epäonnistumisen UI.
Kirjautumisfunktio suoritetaan nyt seuraavasti:
- Sovellus kutsuu
login()
-funktiotaView
-kerroksestaView
pääsäikeessä. -
launch
luo uuden coroutiinin verkkopyynnön tekemistä varten pääsäikeessä, ja coroutiini aloittaa suorituksen. - Korutiinin sisällä kutsu
loginRepository.makeLoginRequest()
now keskeyttää korutiinin jatkosuorituksen, kunnesmakeLoginRequest()
:ssä olevawithContext
lohkomakeLoginRequest()
lopettaa suorituksen. - Kun
withContext
-lohko päättyy,login()
:ssä oleva korutiinilogin()
jatkaa suoritusta pääsäikeessä verkkopyynnön tuloksella.
Poikkeusten käsittely
Käsitelläksesi poikkeuksia, joita Repository
-kerros voi heittää, käytä Kotlinin sisäänrakennettua tukea poikkeuksille.Seuraavassa esimerkissä käytämme try-catch
-lohkoa:
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 } } }}
Tässä esimerkissä kaikki odottamattomat poikkeukset, jotka makeLoginRequest()
kutsu heittää, käsitellään virheenä käyttöliittymässä.
Leave a Reply