Android e tecniche multithreading

di  Antonio Coschignano, mercoledì 22 febbraio 2012

Ogni volta che si avvia un'applicazione Android, viene creato automaticamente un thread chiamato 'main'. Il thread principale, chiamato anche il thread dell'interfaccia utente, è molto importante perchè si occupa di spedire gli eventi appropriati ai diversi widgets e anche agli eventi di disegno. E' anche il thread che permette di interagire con i widget di Android. Ad esempio, se si tocca un tasto sullo schermo, il thread UI genera ed invia un evento al widget che a sua volta definisce il suo stato premuto e invalida una richiesta alla coda degli eventi. Il thread UI estrae la richiesta e notifica al widget di ridisegnarsi.

Questo modello con un unico thread può produrre scarse prestazioni nelle applicazioni Android, se non si considerano alcune particolarità. Dal momento che tutto avviene su un singolo thread - eseguire operazioni lunghe, come l'accesso alla rete o query di database su questo thread - blocca l'intera interfaccia utente. Nessun evento può essere inviato, inclusi gli eventi di disegno, mentre è in corso l'operazione effettuata. Dal punto di vista dell'utente, l'applicazione appare bloccata. Ancora peggio, se il thread dell'interfaccia utente è bloccato per più di pochi secondi (circa 5 secondi per il momento) all'utente appare la famigerata finestra di dialogo "L'applicazione non risponde" (ANR). Se volete verificare questa situazione, scrivete una semplice applicazione con un pulsante che richiama un Thread.sleep (2000) all'interno di OnClickListener. Il pulsante rimarrà nel suo stato 'premuto' per circa 2 secondi prima di tornare al suo stato normale. Quando questo accade, è molto facile per l'utente considerare l'applicazione lenta. Quindi è necessario evitare lunghe operazioni sul thread UI.

Per ovviare a questo problema molti programmatori eseguono in background (tramite Thread) queste operazioni, e apparentemente sembra giusto così. Prendiamo l'esempio di OnClickListener per il download di un'immagine attraverso la rete e la visualizzazione all'interno di una ImageView :

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork();
            mImageView.setImageBitmap(b);
        }
    }).start();
}

In un primo momento, questo codice sembra essere una buona soluzione al problema, in quanto non blocca il thread dell'interfaccia utente. Purtroppo, contrasta con il modello dell'unico thread: l'Android UI toolkit non è thread-safe e ogni thread deve sempre essere manipolato sul thread UI. In questo pezzo di codice, la ImageView viene manipolata in un thread esterno, che può causare problemi di inconsistenza. Rintracciare e risolvere tali bug può essere difficile e richiede molto tempo.

Android offre diversi modi per accedere al thread UI da altri thread. Si può già avere familiarità con alcuni di loro, questo è un elenco completo:

Ognuna di queste classi e metodi potrebbero essere utilizzati per correggere il precedente esempio di codice:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
           final Bitmap b = loadImageFromNetwork();
           mImageView.post(new Runnable() {
               public void run() {
                   mImageView.setImageBitmap(b);
               }
           });
        }
    }).start();
}

Purtroppo, queste classi/metodi tendono a rendere il codice più complicato e più difficile da leggere. Diventa ancora peggio quando si implementano operazioni complesse che richiedono frequenti aggiornamenti dell'interfaccia utente. Per ovviare a questo problema, a partire da Android 1.5 esiste una nuova classe di utilità, chiamata AsyncTask , che semplifica la creazione di operazioni a lunga durata che devono comunicare con l'interfaccia utente.

La classe AsyncTask

AsyncTask è disponibile anche per Android 1.0 e 1.1 con il nome UserTask. L'obiettivo di AsyncTask è quello di prendersi cura della gestione dei thread. Il nostro esempio precedente può essere facilmente riscritto con AsyncTask :

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask {
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

Come potete vedere, AsyncTask deve essere utilizzato come inner class, cioè all'interno dell'Activity. E 'anche molto importante ricordare che un'istanza di AsyncTask deve essere creata sul thread dell'interfaccia utente e può essere eseguita una sola volta. E' possibile leggere la documentazione di AsyncTask per una piena comprensione su come utilizzare questa classe. Ma vediamo una rapida panoramica di come funziona:

  • E' possibile specificare il tipo, utilizzando i Generics, i parametri, i valori di progresso e il valore finale dell'operazione
  • Il metodo doInBackground () esegue automaticamente un thread
  • OnPreExecute () , OnPostExecute () e onProgressUpdate () sono tutti richiamati sul thread UI
  • Il valore restituito da doInBackground () viene inviato al OnPostExecute ()
  • E' possibile chiamare publishProgress() in qualsiasi momento in doInBackground () per eseguire onProgressUpdate() sul thread UI
  • E' possibile annullare l'operazione in qualsiasi momento, da qualsiasi thread

Indipendentemente dal fatto che si utilizza o meno AsyncTask, ricordate sempre queste due regole sul modello unico dell'unico thread: non bloccare il thread dell'interfaccia utente e assicurarsi che ogni operazione sulla GUI viene eseguita solo sul thread UI. AsyncTask rende solo più facile fare entrambe le cose.

Questo'articolo e la traduzione dall'inglese di Painless threading sul blog ufficiale di Google Android.

Altri link che potrebbero interessarti
  • » Struttura di un'applicazione Android
  • » Screenshot su un dispositivo Android
  • » Pubblicare applicazioni nell'Android Market
  • » Integrare Google Analytics nelle applicazioni Android
  • » Integrare AdMob nelle applicazioni Android
  • » Guida all'installazione dell'Android SDK
  • » Guida agli Adapter e le ListView in Android
  • » Gestire Thread e GUI nelle applicazioni Android
  • » Applicazioni Android ed il supporto multilingue
  • » Android VideoView e le Youtube Api
  • » Android Linkify e i collegamenti testuali
  • » Android HTML TextView
  • » Android e la gestione dei sensori
  • » Android e l'interfaccia Parcelable