Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Multithreading in Android con AsyncTack

Come gestire i thread in Android con AsyncTack e garantire la reattività della propria applicazione mobile
Come gestire i thread in Android con AsyncTack e garantire la reattività della propria applicazione mobile
Link copiato negli appunti

Questo articolo riguarda le operazioni in background in Android e il punto di partenza è il concetto di reattività delle applicazioni. Se ne illustra la centralità e si offre una panoramica riepilogativa degli strumenti utili ad ottenerla evidenziando gli aspetti su cui vale la pena concentrare la propria attenzione. Infine viene sviluppato un piccolo esempio, scaricabile dal link "Scarica allegato" in alto in questa pagina, che impiega la classe AsyncTask per accedere a dati di Borsa disponibili in Rete e mostrarli in una Activity.

Applicazioni reattive

La "reattività" di un'applicazione dovrebbe essere uno dei principali obiettivi di uno sviluppatore Android. Ma cosa si intende per reattività? La capacità dell'applicazione di reagire agli input dell'utente nella maniera più immediata possibile. In particolare Android è piuttosto rigido in fatto di reattività dei software. Si pensi al temuto messaggio ANR, "Application Not Responding", che viene lanciato non appena un'applicazione - secondo il modo di vedere di Android - non risulta sufficientemente reattiva ai comandi dell'utente. Generalmente questa situazione produce una finestra di dialogo - come quella mostrata in figura - che chiede all'utente se vuole aspettare la risposta dell'applicazione o se desidera chiuderla.

Applicazione non risponde

Per avere un messaggio di questo tipo, è sufficiente che Android rilevi un tempo di risposta intorno ai 5 secondi. 5 secondi: niente per la dimensione dell'essere umano, molto per un sistema informatico, specialmente uno come Android che mira a fornire user experience più gradevoli possibile.Come garantire, quindi, reattività? Sicuramente la via che non va seguita è quella di impoverire la nostra applicazione limitandone le funzionalità. Infatti un conto è scegliere "cosa" far fare ad un software, tutt'altro è farglielo fare in maniera reattiva.Dobbiamo necessariamente organizzare meglio il lavoro. Questo è il momento in cui la strada della reattività incrocia i thread.

Cosa sono i thread?

I thread sono quel costrutto informatico che un po' tutti conoscono (in teoria) ma quasi nessuno ha mai usato. O almeno: quasi nessuno lo sfrutta con padronanza. Tecnicamente, un thread è la più piccola unità di istruzioni che può essere gestita dallo scheduler di un sistema operativo. In pratica, ogni volta che viene lanciato un programma in un sistema informatico nasce un processo il quale a sua volta contiene vari flussi di esecuzione: i thread, appunto.

I thread sono indispensabili quando si vuole che il proprio programma svolga operazioni in parallelo. Immaginiamo che si debbano scaricare dati da Internet per poi farne il parsing ed estrapolarne informazioni. Se non usassimo più thread saremmo costretti a svolgere i due lavori sequenzialmente: prima dovremmo scaricare i dati, poi una volta conclusa questa operazione potremmo passare a farne il parsing. In un contesto multithreading invece si hanno nel programma più anime "operative" che si alternano in esecuzione portando avanti lavori di natura diversa che collaborano l'uno con l'altro.

L'interfaccia utente in Android lavora interamente sul thread principale dell'applicazione detto main thread o UI-thread (proprio perchè è il thread della User-Interface). Ogni operazione più lenta o soprattutto quelle che sono soggette a tempi di latenza variabili ed indipendenti dalla nostra applicazione (pensiamo allo scaricamento in rete più o meno veloce a seconda del traffico) andrebbe eseguita su un thread separato che in genere viene chiamato worker thread. Android è molto chiaro in materia, dettando due regole fondamentali:

  1. non bloccare mai lo UI thread con operazioni lunghe: sarebbe la morte della reattività della nostra applicazione. Lo UI thread è prevalentemente deputato alla gestione dei messaggi di sistema riguardanti l'interfaccia utente;
  2. un worker thread deve svolgere solo lavoro in background e non modificare mai l'interfaccia utente direttamente.

Queste due regole evidenziano l'importanza di avere tanto thread alternativi quanto meccanismi idonei ad attuare un'opportuna comunicazione tra di essi.

Usare i thread in Android

Ricordiamo che un programma Android è prima di tutto un programma Java. Questo si vede non solo per la sintassi del nostro codice ma soprattutto perchè in genere possiamo sfruttare la maggior parte degli strumenti che abbiamo a disposizione nel framework Java: stream, strutture dati e via dicendo. Per i thread vale lo stesso discorso. Possiamo gestire i thread in Android come si fa in Java: dichiariamo un oggetto Thread o implementiamo un'interfaccia Runnable, inseriamo nell'implementazione del metodo run() le operazioni che vogliamo gestire sul thread separato e lo lanciamo.

Tutto ciò non è detto che crei necessariamente difficoltà al programmatore però è importante che venga fatto bene. Vanno separate su un thread a sè stante le operazioni giuste, il thread va terminato nel modo appropriato e la sincronizzazione deve funzionare correttamente senza conflitti di accesso alle risorse. Inoltre, ci sono le due regole da rispettare, quelle enunciate al paragrafo precedente. E su questo Android è categorico.

A questo punto viene da chiedersi: ma non c'è un modo un pò comodo per gestire i thread nel pieno rispetto dei dettami di Android in materia? Sì, c'è. Viene trattato nel prossimo paragrafo e risponde al nome di AsyncTack.

Multithreading con AsyncTask

AsyncTask è una classe offerta dal framework all'interno del package android.os. È specializzata nell'elaborazione di operazioni su un thread separato permettendo al programmatore di attuare aggiornamenti periodici sull'interfaccia utente.

Osserviamo innanzitutto che AsyncTask richiede l'implementazione di un metodo, denominato doInBackground destinato a contenere l'insieme delle operazioni da eseguire su un thread secondario. Questo metodo corrisponde al run() che siamo abituati ad usare in un oggetto che implementa Runnable.

Inoltre ci sono alcuni metodi la cui implementazione non è obbligatoria ma che risultano spesso indispensabili al corretto funzionamento della nostra applicazione soprattutto perchè si occupano della comunicazione con lo UI-thread.

Si tratta di:

  1. onPreExecute: viene eseguito prima di doInBackground e serve a configurare oggetti utili al suo lavoro o alla comunicazione con lo UI-thread;
  2. onProgressUpdate: viene invocato dall'interno di doInbackground, durante la sua elaborazione, mediante l'invocazione di publishProgress. Ogni chiamata a quest'ultimo metodo produce una chiamata a onProgressUpdate. Questo è il luogo deputato alle operazioni di aggiornamento dell'interfaccia utente nel corso (e non al termine!) della lavorazione del thread in background, pensiamo ad esempio ad una barra di progresso che periodicamente indica il livello di avanzamento delle operazioni in background;
  3. onPostExecute: il metodo conclusivo. Viene chiamato solo quando doInbackground è concluso e ha lo scopo di pubblicare sull'interfaccia utente i risultati definitivi dell'elaborazione in background oltre che ad attuare le opportune chiusure di connessioni e rilascio di risorse;
  4. onCanceled: invocato quando il task è stato interrotto.

Abbiamo visto che alcuni dei metodi precedenti si occupano di comunicazione tra thread perciò è necessario vedere quali sono i tipi di dato coinvolti. AsyncTask ha una forma "parametrica" nel senso che è stata progettata secondo lo standard dei Generics Java. Quando si vuole definire un task in background è necessario estendere AsyncTask<Params, Progress, Result>, specificando quali classi implementeranno i tre parametri.

In particolare:

  1. Params: è il tipo di oggetti che saranno passati dallo UI-thread al metodo doInBackground di AsyncTask. In pratica si tratta degli oggetti in input al task da svolgere in background;
  2. Progress: rappresenta il tipo di oggetti passati dall'interno di doInBackground a onProgressUpdate tramite publishProgress;
  3. Result: il tipo di dato del risultato finale del lavoro in background. È il tipo di ritorno di doInBackground e verrà direttamente passato come input a OnPostExecute.

I parametri di AsyncTask devono essere sempre tre, se non sono tutti necessari li si sostituirà con Void. Notare che questa keyword ha l'iniziale maiuscola, non deve pertanto essere confusa con void. La classe Void serve a mettere un "niente" dove sarebbe richiesto un oggetto. Possiamo considerarlo un segnaposto per oggetti mancanti.

Esempio pratico: scaricare dati da Piazza Affari

Una delle attività più comuni da risolvere in un worker thread è l'accesso alla rete, considerate le tempistiche cui è soggetta.

Realizziamo un piccolo esempio con un minimo di valenza pratica. Immaginiamo che la nostra app sia costituita da una Activity che, a richiesta dell'utente, debba scaricare l'ultima valorizzazione disponibile del principale indice della Borsa di Milano, il FTSE-MIB, e di mostrarla nel layout. I dati che utilizzeremo saranno prelevati da Yahoo! Finanza mediante uno degli appositi link messi a disposizione dal portale.

L'indirizzo Internet corrispondente al link ha una forma del tipo:

 http://download.finance.yahoo.com/d/quotes.csv?s=FTSEMIB.MI&f=sl1d1t1c1ohgv&e=.csv

e nel codice di esempio viene riportato hardcoded all'interno della stringa address nel metodo doInBackground. Una parte fondamentale da affrontare dopo lo scaricamento dei dati è il loro parsing, ossia l'interpretazione ed il conseguente inserimento in oggetti Java. I formati più comuni che il programmatore si trova a dover affrontare sono JSON e XML (con relativi "dialetti").

In questo caso, si avrà un formato molto più semplice infatti tutti i dati - nome dell'indice, valore, data e vari massimi, minimi e variazioni - sono concatenati in una stringa in cui viene usata la virgola come separatore. Ad esempio, il risultato potrebbe essere il seguente:

"FTSEMIB.MI",18222.420,"12/16/2013","11:30am",+416.689,17811.070,18222.420,17780.211,0

AsyncTask viene estesa in una classe denominata BackgroundTask interna all'Activity.

private class BackgroundTask extends AsyncTask<Void, Void, Values>
{
	@Override
	protected Values doInBackground(Void... arg0)
	{
	  Values v=null;
	  String address="http://download.finance.yahoo.com/d/quotes.csv?s=FTSEMIB.MI&f=sl1d1t1c1ohgv&e=.csv";
	  try
		{
		  URL url=new URL(address);
		  BufferedReader rd = new BufferedReader(new InputStreamReader(url.openStream()));
		  String line = rd.readLine();
		  v=new Values(line);
		}
		catch (IOException e)
		{ }
	 return v;
	}
@Override
protected void onPostExecute(Values result)
{
	super.onPostExecute(result);
	updateLayout(result);
}
}

Come si può vedere dallo stralcio di codice l'attività di Rete in sè stessa viene svolta in una maniera molto semplice, usando la classe URL. Si ricordi comunque che affinchè l'applicazione possa fare accesso alla Rete è necessario includere nel file manifest, AndroidManifest.xml, una permission come la seguente:

<uses-permission android:name="android.permission.INTERNET"/>

Come già detto la cosa importante da sottolineare è che tutto il contenuto del metodo doInBackground non verrà svolto sul thread principale dell'applicazione ma su uno alternativo. Dopo lo scaricamento, l'oggetto line conterrà la stringa con la concatenazione dei dati richiesti. La classe Values si occupa del parsing della stringa in questione ed è definita nel file stesso. Non viene mostrata nel codice visto che include normali operazioni Java di splitting di stringhe, sostituzione di caratteri ed interpretazione di date.

Altro aspetto cui fare attenzione è il modo in cui avviene la comunicazione tra thread. L'oggetto Values già nel suo costruttore verrà completato con i dati provenienti dalla Rete e costituirà il valore di ritorno di doInBackground. Al contempo esso fornirà il parametro in ingresso di onPostExecute che, al contrario, verrà eseguito sullo UI-thread ed è proprio per questo che dal suo interno sarà possibile invocare updateLayout che contiene normali operazioni di aggiornamento del layout.

L'esempio offrirà il risultato grafico riportato in figura.

Screenshot dell'applicazione

Screenshot dell'applicazione

Indipendentemente dalla sua utilità pratica, l'esempio evidenzia qual è il motivo per cui vale la pena conoscere bene l'utilizzo di AsyncTask: la completa integrazione di un thread alternativo nel programma ed un meccanismo di comunicazione che lo incorpori completamente nel lavoro della Activity.

Per concludere, si noti che il pulsante presente nell'interfaccia utente avvia il lavoro di AsyncTask con le seguenti righe:

public void start(View v)
	{
		BackgroundTask task=new BackgroundTask();
		task.execute();
	}

Ogni volta che si necessita di attivare il task in background si deve istanziare un nuovo oggetto. Infatti gli AsyncTask non sono riciclabili pertanto risulta inutile conservarne un riferimento per poi tentare una nuova invocazione di execute.

Thread, Service, AsyncTask: quale usare?

Il programmatore che estende le proprie conoscenze nel mondo Android nel tempo scopre una serie di strumenti utili per eseguire attività asincrone. Solo in questo articolo si è discussa l'importanza dei thread e l'impiego di AsyncTask. Ma spesso si legge a proposito dei Service, utili allo stesso scopo, e caratterizzati da un ruolo niente affatto marginale tanto da costituire uno dei quattro componenti fondamentali del framework insieme ad Activity, Content Provider e Broadcast Receiver.

Quindi il programmatore si potrebbe chiedere: cosa dovrei usare per le operazioni in background? Normali Thread, AsyncTask o Service? È molto importante non solo conoscere gli strumenti ma anche comprenderne l'appropriato campo di utilizzo.

I Service sono fondamentali per lavori lunghi, per dei veri servizi in background come denuncia il loro stesso nome. Possono interagire in vari modi con le applicazioni del sistema ma il loro scopo è staccare l'attività in background dal ciclo di vita dell'Activity. Ciò non succede con AsyncTask. Questa è finalizzata allo svolgimento di "task" in background ossia lavori molto veloci (al più alcuni secondi come indica la documentazione ufficiale) per un uso che potremmo definire sporadico. L'esempio trattato ne mostra un utilizzo piuttosto tipico: gestione di attività "lunghe", svolte in background, non indipendenti dall'interfaccia utente ma, al contrario, impiegate come suo supporto.

Ti consigliamo anche