Coroutine kotlin consentono di scrivere codice asincrono chiaro e semplificato che la tua app reattiva durante la gestione di attività di lunga durata come le chiamate di rete per le operazioni su disco.
Questo argomento fornisce uno sguardo dettagliato sulle coroutine su Android. Se non conoscete le coroutine, assicuratevi di leggere Kotlin coroutines su Android prima di leggere questo argomento.
Gestisci attività di lunga durata
Le coronetine si basano su funzioni regolari aggiungendo due operazioni per gestire
per attività di lunga durata. Oltre a invoke
(o call
) e return
,
le coroutine aggiungono suspend
e resume
:
suspend
mette in pausa l'esecuzione della coroutine attuale, salvando tutti i contenuti locali come la codifica one-hot delle variabili categoriche.resume
continua l'esecuzione di una coroutine sospesa dal luogo in cui è stato sospeso.
Puoi chiamare le funzioni di suspend
solo da altre funzioni di suspend
oppure
utilizzando uno strumento per la creazione di coroutine come launch
per iniziare una nuova coroutine.
L'esempio seguente mostra una semplice implementazione della coroutine per una un'attività ipotetica a lunga esecuzione:
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
In questo esempio, get()
viene ancora eseguito sul thread principale, ma sospende la
coroutine prima di avviare la richiesta di rete. Quando la richiesta di rete
viene completata, get
ripristina la coroutina sospesa invece di utilizzare un callback
per inviare una notifica al thread principale.
Kotlin utilizza un frame stack per gestire la funzione in esecuzione con qualsiasi variabile locale. Quando sospendi una coroutine, lo stack attuale il frame viene copiato e salvato per un secondo momento. Quando riprendi, lo stack frame viene copiato da dove era stata salvata, e la funzione riprenderà a essere eseguita. Anche se il codice può sembrare un normale blocco sequenziale richiesta, la coroutine garantisce che la richiesta di rete eviti il blocco nel thread principale.
Usa le coroutine per la massima sicurezza
Le coroutine Kotlin utilizzano i corrieri per determinare quali thread vengono utilizzati l'esecuzione della coroutine. Per eseguire il codice al di fuori del thread principale, puoi dire a Kotlin coroutine per eseguire il lavoro con il supervisore predefinito o IO. Nella Kotlin, tutte le coroutine devono correre in un centralino, anche quando stanno correndo nel thread principale. Le coroutine possono sospendersi e il supervisore sta responsabile di ripristinarli.
Per specificare dove devono correre le coroutine, Kotlin fornisce tre committenti che puoi utilizzare:
- Dispatchers.Main: utilizza questo supervisore per eseguire una coroutine sul circuito principale
Thread Android. Deve essere usato solo per interagire con l'UI e
quando si lavora rapidamente. Gli esempi includono la chiamata delle funzioni
suspend
, l'esecuzione Operazioni del framework UI Android e aggiornamentoLiveData
oggetti. - Dispatchers.IO: questo supervisore è ottimizzato per eseguire operazioni su disco o rete I/O al di fuori del thread principale. Alcuni esempi includono l'utilizzo Componente sala, lettura o scrittura di file ed esecuzione di operazioni di rete.
- Dispatchers.Default: questo supervisore è ottimizzato per le prestazioni Lavoro ad alta intensità di CPU al di fuori del thread principale. Esempi di casi d'uso includono l'ordinamento di elenco e analisi del file JSON.
Facendo sempre l'esempio precedente, puoi utilizzare i committenti per ridefinire i
Funzione get
. Nel corpo di get
, chiama withContext(Dispatchers.IO)
per
crea un blocco che viene eseguito sul pool di thread di I/O. Qualsiasi codice che metti al suo interno
viene sempre eseguito tramite il committente IO
. Poiché withContext
è di per sé
funzione di sospensione, la funzione get
è anche una funzione di sospensione.
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */ // Dispatchers.IO (main-safety block)
} // Dispatchers.Main
}
Con le coroutine, puoi inviare thread con un controllo granulare. Poiché
withContext()
consente di controllare il pool di thread di qualsiasi riga di codice senza
introducendo i callback, puoi applicarli a funzioni molto piccole come
da un database o eseguire una richiesta di rete. Una buona prassi è utilizzare
withContext()
per verificare che ogni funzione sia protetta da principale, il che significa che
può chiamare la funzione dal thread principale. In questo modo, il chiamante non deve mai
pensa a quale thread dovrebbe essere usato per eseguire la funzione.
Nell'esempio precedente, fetchDocs()
viene eseguito sul thread principale. ma
può chiamare in modo sicuro get
, che esegue una richiesta di rete in background.
Poiché le coroutine supportano suspend
e resume
, le coroutine sul
il thread viene ripreso con il risultato get
non appena il blocco withContext
viene
fatto.
Rendimento di withContext()
withContext()
non aumenta il sovraccarico rispetto a un equivalente basato su callback
implementazione. Inoltre, è possibile ottimizzare le chiamate withContext()
al di là di un'implementazione equivalente basata su callback in alcune situazioni. Per
Ad esempio, se una funzione effettua dieci chiamate a una rete, puoi dire a Kotlin
cambiare thread una sola volta usando un withContext()
esterno. Poi, anche se
la libreria di rete utilizza withContext()
più volte, rimane nella stessa
il supervisore ed evita di cambiare thread. Inoltre, Kotlin ottimizza il passaggio
tra Dispatchers.Default
e Dispatchers.IO
per evitare i cambi di thread
ove possibile.
Inizia una coroutine
Puoi iniziare le coroutine in uno dei due modi seguenti:
launch
avvia una nuova coroutine e non restituisce il risultato al chiamante. Qualsiasi lavoro considerato "fuoco e dimentica" può essere avviato utilizzandolaunch
.async
avvia una nuova coroutine e ti consente di restituire un risultato con una sospensione funzione chiamataawait
.
Di solito, devi launch
una nuova coroutina da una funzione regolare,
Come una funzione normale, non può chiamare await
. Usa async
solo se all'interno
un'altra coroutina o l'interno di una funzione di sospensione e l'esecuzione
la decomposizione parallela.
Decomposizione parallela
Tutte le coroutine avviate all'interno di una funzione suspend
devono essere interrotte quando
questa funzione restituisce, quindi è probabile che tu debba garantire che le coroutine
terminare prima di tornare. Con la contemporaneità strutturata in Kotlin, puoi definire
un coroutineScope
che avvia una o più coroutine. Quindi, utilizzando await()
(per una singola coroutine) o awaitAll()
(per più coroutine), puoi
garantire che queste coroutine finiscano prima di tornare dalla funzione.
Ad esempio, definiamo un'istruzione coroutineScope
che recupera due documenti
in modo asincrono. Chiamando await()
su ogni riferimento differito, garantiamo
che entrambe le operazioni di async
terminino prima di restituire un valore:
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
Puoi utilizzare awaitAll()
anche nelle raccolte, come mostrato nell'esempio seguente:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main)
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
)
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
Anche se fetchTwoDocs()
lancia nuove coroutine con async
, la funzione
usa awaitAll()
per attendere che le coroutine lanciate finiscano prima
che ritornano. Tuttavia, tieni presente che anche se non avessimo chiamato awaitAll()
,
Il generatore di coroutineScope
non riprende la coroutine che ha chiamato
fetchTwoDocs
al termine di tutte le nuove coroutine.
Inoltre, coroutineScope
rileva eventuali eccezioni lanciate dalle coroutine
e lo instrada al chiamante.
Per ulteriori informazioni sulla decomposizione parallela, vedi Composizione di funzioni di sospensione.
Concetti sulle coroutine
CoroutineScope
Un CoroutineScope
tiene traccia di qualsiasi coroutine creata utilizzando launch
o async
. La
il lavoro in corso (ovvero le coroutine in esecuzione) può essere annullato chiamando
scope.cancel()
in qualsiasi momento. In Android, alcune librerie KTX forniscono
il proprio CoroutineScope
per determinate classi del ciclo di vita. Ad esempio:
ViewModel
ha un
viewModelScope
,
e Lifecycle
ha lifecycleScope
.
Tuttavia, a differenza di un supervisore, un CoroutineScope
non si occupa delle coroutine.
viewModelScope
viene utilizzato anche negli esempi della
Threading in background su Android con Coroutines.
Tuttavia, se devi creare il tuo CoroutineScope
per controllare il
ciclo di vita delle coroutine in un determinato livello dell'app, puoi crearne uno
come segue:
class ExampleClass {
// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine within the scope
scope.launch {
// New coroutine that can call suspend functions
fetchDocs()
}
}
fun cleanUp() {
// Cancel the scope to cancel ongoing coroutines work
scope.cancel()
}
}
Un ambito annullato non può creare altre coroutine. Pertanto, devi
chiama scope.cancel()
solo quando la classe che ne controlla il ciclo di vita
viene distrutto. Quando utilizzi viewModelScope
, il parametro
Il corso ViewModel
annulla il corso
automaticamente l'ambito nel metodo onCleared()
di ViewModel.
Job
Un Job
è un handle di una coroutine. Ogni coroutine che crei con launch
oppure async
restituisce un'istanza Job
che identifica in modo univoco
coroutine e ne gestisce il ciclo di vita. Puoi anche passare Job
a un
CoroutineScope
per gestire ulteriormente il suo ciclo di vita, come mostrato di seguito
esempio:
class ExampleClass {
...
fun exampleMethod() {
// Handle to the coroutine, you can control its lifecycle
val job = scope.launch {
// New coroutine
}
if (...) {
// Cancel the coroutine started above, this doesn't affect the scope
// this coroutine was launched in
job.cancel()
}
}
}
Contesto Coroutine
Un CoroutineContext
definisce il comportamento di una coroutine utilizzando il seguente insieme di elementi:
Job
: Controlla il ciclo di vita della coroutina.CoroutineDispatcher
: Le email vengono inviate al thread appropriato.CoroutineName
: Il nome della coroutine, utile per il debug.CoroutineExceptionHandler
: Consente di gestire le eccezioni non rilevate.
Per le nuove coroutine create in un ambito, viene creata una nuova istanza Job
assegnati alla nuova coroutine e agli altri elementi CoroutineContext
vengono ereditati dall'ambito contenitore. Puoi eseguire l'override
passando un nuovo CoroutineContext
a launch
o async
personalizzata. Tieni presente che passare un valore di Job
a launch
o async
non ha alcun effetto.
poiché una nuova istanza di Job
viene sempre assegnata a una nuova coroutine.
class ExampleClass {
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine on Dispatchers.Main as it's the scope's default
val job1 = scope.launch {
// New coroutine with CoroutineName = "coroutine" (default)
}
// Starts a new coroutine on Dispatchers.Default
val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) {
// New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
}
}
}
Risorse aggiuntive sulle coroutine
Per ulteriori risorse sulle coroutine, consulta i seguenti link: