Una coroutine è un pattern di progettazione di contemporaneità che puoi utilizzare su Android per semplificare il codice eseguito in modo asincrono. Coroutine sono stati aggiunti a Kotlin nella versione 1.3 e si basano su concetti da altre lingue.
Su Android, le coroutine aiutano a gestire le attività di lunga durata che potrebbero altrimenti il thread principale verrà bloccato e la tua app non risponde. Oltre il 50% degli sviluppatori professionisti che utilizzano le coroutine ha dichiarato di aver visto di produttività in più. Questo argomento descrive come utilizzare le coroutine Kotlin per affrontare questi risolvere problemi, permettendoti di scrivere codice dell'app più chiaro e conciso.
Funzionalità
Coroutines è la nostra soluzione consigliata per la programmazione asincrona su Android. Ecco alcune funzionalità degne di nota:
- Leggera: puoi eseguire molte coroutine in un singolo thread a causa assistenza per sospensione, che non blocca il thread su cui è in esecuzione la coroutine. In fase di sospensione risparmia memoria rispetto al blocco e supporta molte operazioni simultanee.
- Meno fughe di memoria: utilizza contemporaneità strutturata eseguire operazioni in un ambito.
- Supporto dell'annullamento integrato: Annullamento viene propagata automaticamente attraverso la gerarchia di coroutine in esecuzione.
- Integrazione di Jetpack: molte librerie Jetpack includono estensioni che forniscono supporto completo per le coroutine. Alcune le biblioteche offrono anche il loro ambito coroutine che puoi da utilizzare per la contemporaneità strutturata.
Panoramica degli esempi
In base alla Guida all'architettura delle app, gli esempi in questo argomento effettua una richiesta di rete e restituisce il risultato all'istanza in cui l'app può quindi mostrare il risultato all'utente.
In particolare, la ViewModel
Il componente architettura chiama il livello di repository sul thread principale a
attivare la richiesta di rete. Questa guida esegue l'iterazione di varie soluzioni
che utilizzano coroutine per mantenere sbloccato il thread principale.
ViewModel
include un insieme di estensioni KTX che funzionano direttamente con
coroutine. Queste estensioni sono
lifecycle-viewmodel-ktx
e vengono utilizzati
in questa guida.
Informazioni sulle dipendenze
Per usare le coroutine nel tuo progetto Android, aggiungi la seguente dipendenza
al file build.gradle
della tua app:
Groovy
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Esecuzione in un thread in background
Quando viene inviata una richiesta di rete sul thread principale, quest'ultimo attende o blocca
finché non riceve una risposta. Poiché il thread è bloccato, il sistema operativo non viene
in grado di chiamare onDraw()
, causando il blocco dell'app e la possibilità
apre la finestra di dialogo ANR (L'applicazione non risponde). Per un utente migliore
eseguiamo questa operazione su un thread in background.
Innanzitutto, diamo un'occhiata alla lezione Repository
e vediamo come si
effettuando la richiesta di rete:
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
è sincrono e blocca il thread di chiamata. Per modellare
la risposta alla richiesta di rete, abbiamo la nostra classe Result
.
L'elemento ViewModel
attiva la richiesta di rete quando l'utente fa clic, ad esempio
esempio, su un pulsante:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
Con il codice precedente, LoginViewModel
blocca il thread dell'interfaccia utente quando
effettuando la richiesta di rete. La soluzione più semplice per spostare l'esecuzione
dal thread principale è creare una nuova coroutine ed eseguire la rete
su un thread di I/O:
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)
}
}
}
Analizziamo il codice delle coroutine nella funzione login
:
viewModelScope
è unCoroutineScope
predefinito incluso in le estensioni KTXViewModel
. Tieni presente che tutte le coroutine devono essere in l'ambito di attività. UnCoroutineScope
gestisce una o più coroutine correlate.launch
è una funzione che crea una coroutine e invia il dell'esecuzione del proprio corpo della funzione al committente corrispondente.Dispatchers.IO
indica che la coroutine deve essere eseguita su una riservato alle operazioni di I/O.
La funzione login
viene eseguita come segue:
- L'app chiama la funzione
login
dal livelloView
del thread principale. launch
crea una nuova coroutine e viene effettuata la richiesta di rete in modo indipendente su un thread riservato per le operazioni di I/O.- Mentre la coroutine è in esecuzione, la funzione
login
continua l'esecuzione e restituisce un risultato, possibilmente prima del completamento della richiesta di rete. Tieni presente che per semplicità, per ora la risposta della rete viene ignorata.
Poiché la coroutina inizia con viewModelScope
, viene eseguita in
l'ambito di ViewModel
. Se ViewModel
viene eliminato perché
l'utente esce dalla schermata, viewModelScope
sta automaticamente
annullato, così come tutte le coroutine in esecuzione.
Un problema con l'esempio precedente è che qualsiasi operazione chiamata
makeLoginRequest
deve ricordarsi di disattivare esplicitamente l'esecuzione
nel thread principale. Vediamo come possiamo modificare il Repository
per risolvere
questo problema per noi.
Usa le coroutine per la massima sicurezza
Una funzione viene considerata main-safe quando non blocca gli aggiornamenti dell'interfaccia utente nella
thread principale. La funzione makeLoginRequest
non è sicura per la rete principale, in quanto la chiamata
makeLoginRequest
del thread principale blocca la UI. Utilizza la
Funzione withContext()
dalla libreria di coroutine per spostare l'esecuzione
di una coroutina a un thread diverso:
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)
sposta l'esecuzione della coroutine in un
Thread I/O, rendendo la nostra funzione di chiamata sicura per il principale e consentendo all'UI di
aggiornarli in base alle esigenze.
makeLoginRequest
è contrassegnato anche con la parola chiave suspend
. Questa parola chiave
è il metodo di Kotlin di forzare l'applicazione di una funzione in modo che venga richiamata dall'interno di una coroutina.
Nell'esempio seguente, la coroutine viene creata in LoginViewModel
.
Mentre makeLoginRequest
sposta l'esecuzione dal thread principale, la coroutine
nella funzione login
ora può essere eseguito nel thread principale:
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
}
}
}
}
Tieni presente che la coroutine è ancora necessaria qui, dal momento che makeLoginRequest
è
una funzione suspend
e tutte le funzioni suspend
devono essere eseguite in
una coroutine.
Questo codice differisce dall'esempio precedente di login
per due aspetti:
launch
non richiede un parametroDispatchers.IO
. Quando non passaDispatcher
alaunch
, eventuali coroutine lanciate daviewModelScope
eseguito nel thread principale.- Il risultato della richiesta di rete viene ora gestito per mostrare l'esito positivo o un'interfaccia utente con errori.
La funzione di accesso viene ora eseguita nel seguente modo:
- L'app chiama la funzione
login()
dal livelloView
del thread principale. launch
crea una nuova coroutine sul thread principale e inizia la coroutine dell'esecuzione.- Nella coroutine, la chiamata a
loginRepository.makeLoginRequest()
ora sospende ulteriore esecuzione della coroutina fino alwithContext
blocco inmakeLoginRequest()
termina l'esecuzione. - Al termine del blocco
withContext
, la coroutine nella squadralogin()
riprende sul thread principale con il risultato della richiesta di rete.
Gestione delle eccezioni
Per gestire le eccezioni che il livello Repository
può generare, utilizza il comando
supporto integrato per le eccezioni.
Nel seguente esempio, utilizziamo un blocco try-catch
:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(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
}
}
}
}
In questo esempio, le eccezioni impreviste generate dall'elemento makeLoginRequest()
viene gestita come un errore nella UI.
Risorse aggiuntive sulle coroutine
Per informazioni più dettagliate sulle coroutine su Android, vai su Migliora le prestazioni dell'app con le coroutine Kotlin.
Per ulteriori risorse sulle coroutine, consulta i seguenti link:
- Panoramica di Coroutines (JetBrains)
- Guida alle coroutine (JetBrains)
- Risorse aggiuntive per coroutine e flussi di Kotlin