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 ViewModelArkkitehtuurikomponentti 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ääritelty CoroutineScope, joka sisältyy ViewModel 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-funktiota View-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 makeLoginRequeston suspend-funktio, ja kaikki suspend-funktiot on suoritettava coroutinessa.

Tämä koodi eroaa edellisestä login esimerkistä muutamalla tavalla:

  • launch ei ota Dispatchers.IO parametria. Kun Dispatcher ei välitä launch:lle Dispatcher, kaikki viewModelScope: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()-funktiota View-kerroksesta View 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, kunnes makeLoginRequest():ssä oleva withContextlohko makeLoginRequest() lopettaa suorituksen.
  • Kun withContext-lohko päättyy, login():ssä oleva korutiini login() 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