Questo fa parte di una serie in più parti sull’uso delle coroutine su Android. Questo post si concentra su come funzionano le coroutine e quali problemi risolvono.
Altri articoli di questa serie:
Le coroutine Kotlin introducono un nuovo stile di concorrenza che può essere usato su Android per semplificare il codice asincrono. Mentre sono nuove per Kotlin nella 1.3, il concetto di coroutine è stato in giro fin dagli albori dei linguaggi di programmazione. Il primo linguaggio ad esplorare l’uso delle coroutine è stato Simula nel 1967.
Negli ultimi anni, le coroutine sono cresciute in popolarità e sono ora incluse in molti linguaggi di programmazione popolari come Javascript, C#, Python, Ruby e Go per nominarne alcuni. Le coroutine di Kotlin si basano su concetti consolidati che sono stati utilizzati per costruire applicazioni di grandi dimensioni.
Su Android, le coroutine sono una grande soluzione a due problemi:
- I compiti a lunga esecuzione sono compiti che richiedono troppo tempo per bloccare il thread principale.
- La Main-safety permette di garantire che qualsiasi funzione di sospensione possa essere chiamata dal thread principale.
Tuffiamoci in ognuna di esse per vedere come le coroutine possono aiutarci a strutturare il codice in modo più pulito!
Long running tasks
Il recupero di una pagina web o l’interazione con un’API implicano entrambi una richiesta di rete. Allo stesso modo, leggere da un database o caricare un’immagine dal disco implica la lettura di un file. Questo genere di cose sono quelle che io chiamo attività di lunga durata – attività che richiedono troppo tempo perché la vostra app si fermi ad aspettarle!
Può essere difficile capire quanto velocemente un telefono moderno esegue il codice rispetto a una richiesta di rete. Su un Pixel 2, un singolo ciclo di CPU richiede poco meno di 0,0000000004 secondi, un numero che è piuttosto difficile da afferrare in termini umani. Tuttavia, se si pensa a una richiesta di rete come a un battito di ciglia, circa 400 millisecondi (0,4 secondi), è più facile capire quanto velocemente opera la CPU. In un battito di ciglia, o in una richiesta di rete un po’ lenta, la CPU può eseguire oltre un miliardo di cicli!
Su Android, ogni app ha un thread principale che si occupa di gestire l’interfaccia utente (come disegnare le viste) e coordinare le interazioni con l’utente. Se c’è troppo lavoro su questo thread, l’app sembra bloccarsi o rallentare, portando a un’esperienza utente indesiderata. Qualsiasi attività di lunga durata dovrebbe essere fatta senza bloccare il thread principale, in modo che la vostra app non mostri ciò che viene chiamato “jank”, come le animazioni congelate, o risponda lentamente agli eventi touch.
Per eseguire una richiesta di rete fuori dal thread principale, un modello comune è quello delle callback. I callback forniscono un handle a una libreria che può essere utilizzato per richiamare il vostro codice in un momento futuro. Con i callback, il recupero di developer.android.com potrebbe apparire così:
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.com") { result ->
show(result)
}
}
}
Anche se get
è chiamato dal thread principale, userà un altro thread per eseguire la richiesta di rete. Poi, una volta che il risultato è disponibile dalla rete, il callback sarà chiamato sul thread principale. Questo è un ottimo modo per gestire compiti di lunga durata, e librerie come Retrofit possono aiutarvi a fare richieste di rete senza bloccare il thread principale.
Usare le coroutine per compiti di lunga durata
Le coroutine sono un modo per semplificare il codice usato per gestire compiti di lunga durata come fetchDocs
. Per esplorare come le coroutine rendono più semplice il codice per i compiti di lunga durata, riscriviamo l’esempio di callback sopra per usare le coroutine.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}// look at this in the next section
suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}
Questo codice non blocca il thread principale? Come fa a restituire un risultato da get
senza aspettare la richiesta di rete e bloccarsi? Si scopre che le coroutine forniscono un modo per Kotlin di eseguire questo codice senza bloccare il thread principale.
Le coroutine si basano sulle funzioni regolari aggiungendo due nuove operazioni. Oltre a invoke (o call) e return, le coroutine aggiungono suspend e resume.
- suspend – mette in pausa l’esecuzione della coroutine corrente, salvando tutte le variabili locali
- resume – continua una coroutine sospesa dal punto in cui è stata messa in pausa
Questa funzionalità è aggiunta da Kotlin tramite la parola chiave suspend sulla funzione. Puoi chiamare le funzioni di sospensione solo da altre funzioni di sospensione, o usando un costruttore di coroutine come launch
per iniziare una nuova coroutine.
Sospendi e riprendi lavorano insieme per sostituire le callback.
Nell’esempio sopra, get
sospenderà la coroutine prima che inizi la richiesta di rete. La funzione get
sarà ancora responsabile dell’esecuzione della richiesta di rete fuori dal thread principale. Poi, quando la richiesta di rete viene completata, invece di chiamare un callback per notificare il thread principale, può semplicemente riprendere la coroutine che ha sospeso.