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:
Alla moda
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: