Gestire Thread e GUI nelle applicazioni Android

di  Antonio Coschignano, giovedì 27 maggio 2010
La gestione del multithreading nell'architettura Android segue il normale modello definito nell'edizione standard di Java. Quindi abbiamo la classe Thread, l'interfaccia Runnable, tutto il package della programmazione concorrente java.util.concurrent. Quindi non ci sono differenze nella definizione di ambienti multithreading.

La differenza invece stà nell'interazione fra thread ed alcuni componenti particolari come l'interfacca grafica. Infatti nell'ambiente Android la modifica o meglio l'aggiornamento dei controlli grafici definiti nella GUI di un'applicazione può essere effettuata solo dall'Activity che rappresenta il main thread. Mentre tutti gli altri thread definiti in un'applicazione detti worker thread non hanno questo privilegio. Se ad esempio dovessimo implementare una TextView in un'applicazione, ed un thread che si occupa di modificarne il testo magari visualizzando il numero di secondi dall'avvio dell'applicazione normalmente siamo portati ad agire in questo modo:
class MyThread extends Thread {
  private TextView view;
  private int i = 0;
  public MyThread(TextView view) {
    this.view = view;
  }
  public void run() {
    try {
      while(true) {
        view.setText("Secondi "+i);//Errore!!
        Thread.sleep(1000);
        i++;
      }
    }catch(InterruptedException ex) {}
  }
}
Questa è la classica situazione che genera un'eccezione che ci avverte che il thread che non ha creato la View non può modificarne lo stato :
threadid=17: thread exiting with uncaught exception (group=0x4001aa28)
Uncaught handler: thread Thread-9 exiting due to uncaught exception
android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
 at android.view.ViewRoot.checkThread(ViewRoot.java:2629)
 ...
Quindi Android ci mette a disposizione un meccanismo particolare per scambiare messaggi tra thread diversi a prescindere del tipo di thread, cioè worker o main thread. Questo è un modello generale che si può adottare in diversi contesti, e si adatta molto bene al nostro caso.

Handler e Message

Il modello prevede un insieme di classi definite nel package android.os che implementa un meccanismo che comprende una coda di messaggi condivisa dal main e worker thread. Il worker thread in questo caso 'produce' i messaggi e il main thread li 'consuma'. Questi messaggi possono tradursi in azioni, cioè il worker thread invia un messaggio che indica di settare un valore ad un controllo grafico ed il main thread lo esegue. Ma come implementare questo meccanismo? Bene, la cosa non è molto complessa come può sembrare. La classe principale che si occupa di gestire questo meccanismo è android.os.Handler:
  • Il main thread (Activity) crea un'istanza dell'Handler ridefinendo il metodo handleMessage(Message msg) dove vengono recapitati i messagi
  • Il worker thread invece ha un riferimento all'Handler ed ogni qualvolta deve notificare un'azione invia un messaggio attraverso il metodo sendMessage(Message msg) dell'oggetto Handler
L'oggetto android.os.Message incapsula la richiesta che viene interpretata dall'Activity. In particolare per ottenere un'istanza della classe Message si usa l'apposito metodo della classe Handler obtainMessage(). L'informazione vera e propria viene inserita in un Bundle che non è altro che una mappa di stringhe del tipo chiave/valore. Vediamo nel dettaglio come costruire un oggetto Message ed inviarlo tramite l'Handler:
Message msg = handler.obtainMessage();
Bundle bundle = new Bundle();
b.putString("chiave", "valore");
msg.setMessage(b);
handler.sendMessage(msg);//Invio del messaggio
Adesso riprendiamo l'esempio che abbiamo visto sopra e cerchiamo di implementare una soluzione corretta. Quindi definiamo il nostro MyThread che in questo caso non riceve più un riferimento al controllo grafico TextView ma ad un Handler, dove dopo ogni secondo inviamo un messaggio che contiene l'informazione da visualizzare nella TextView:
class MyThread extends Thread {
  private Handler handler;
  private int i = 0;
  public MyThread(Handler handler) {
    this.handler = handler;
  }
  public void run() {
    try {
      while(true) {
        notifyMessage("Secondi "+i);
        Thread.sleep(1000);
        i++;
      }
    }catch(InterruptedException ex) {}
  }

  private void notifyMessage(String str) {
    Message msg = handler.obtainMessage();
    Bundle b = new Bundle();
    b.putString("refresh", ""+str);
    msg.setData(b);
    handler.sendMessage(msg);
  }
}
Nel metodo notifyMessage() settiamo nell'oggetto Bundle il messaggio contenuto nella stringa str e la identifichiamo con la stringa 'refresh'. Quest'ultima diciamo che rappresenta il comando con cui si identifica un'azione. Questo è importante poichè in situazioni dove si devono gestire logiche un po più complesse è conveniente definire un protocollo ad hoc di comandi dove a ciascuno è associata un'azione. Adesso implementiamo l'Activity dove definiamo un nostro Handler tramite una inner class:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

public class ExampleActivity extends Activity {

  private TextView view;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    LinearLayout layout = new LinearLayout(this);
    view = new TextView(this);
    layout.addView(view);
    setContentView(layout);
    Handler handler = new MyHandler();
    MyThread thr = new MyThread(handler);
    thr.start();
  }

  private class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
      Bundle bundle = msg.getData();
      if(bundle.containsKey("refresh")) {
        String value = bundle.getString("refresh");
        view.setText(value);
      }
    }
  }
}
Il metodo handleMessage() viene invocato ogni qualvolta il nostro thread invia un messaggio. Quindi estrae il Bundle dall'oggetto Message verifica che sia presente la chiave 'refresh' e ne setta il valore inviato nella TextView.

Questa che abbiamo visto è una situazione abbastanza semplice da implementare. Esistono situazioni un po più complesse dove possiamo avere un thread che aggiorna diversi controlli grafici, oppure diversi thread che invece aggiornano diversi controlli grafici. Le situazioni sono molteplici ma il modello rimane sempre lo stesso. Bisogna fare attenzione nella definizione dei comandi per le azioni, infatti quando diventano troppi possono creare confusione nel codice. In questi casi sarebbe conveniente adottare magari una implementazione di un protocollo dove vengono definite associazioni comando/azione. Ad ogni comando è associata un'azione da compiere definita magari in una classe ed inserita in una mappa che ha come chiave il nome comando.

Questo modello rimane valido per scambi di messagio tra worker e main thread, in particolare quando è il main thread a consumare i messaggi. Lo scenario cambia leggermente quando abbiamo un worker thread che riceve e consuma i messaggi poichè si adottano altri meccanismi dove entra in gioco un particolare oggetto detto Looper.
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
  • » Applicazioni Android ed il supporto multilingue
  • » Android VideoView e le Youtube Api
  • » Android Linkify e i collegamenti testuali
  • » Android HTML TextView
  • » Android e tecniche multithreading
  • » Android e la gestione dei sensori
  • » Android e l'interfaccia Parcelable

  • 3 Commenti per "Gestire Thread e GUI nelle applicazioni Android"

    Autore: AndreaBrandi

    Complimenti per l' articolo davvero ben fatto, chiaro e ben strutturato. Peccato solo che il codice viene visualizzato tutto su una solo riga...

    lunedì 17 gennaio 2011 ore 11:39

    Autore: Morri Luca

    Grazie mille per la guida.

    mercoledì 23 febbraio 2011 ore 13:36

    Autore: alessio

    complimenti per la guida! Sarebbe comodissimo se alla fine dei tutorial fossero allegati gli archivi contenenti il progetto :)

    venerdì 06 gennaio 2012 ore 11:09

    Lascia un commento

    Nome :
    E-mail :
    Commento :

    Tutti i commenti inseriti devono essere approvati da un amministratore prima di essere visualizzati al pubblico. Si tratta di una misura preventiva contro spam e pubblicità e non è necessario reinviare il commento. Si prega di scrivere commenti in tema. Spam e messaggi promozionali non vengono approvati.