Les threads sous Android
Un thread est la plus petite unité d'exécution.
Le temps d'exécution est découpé en tranches ou quantum ou quotas (time slice).
Lorsqu'un thread en exécution a épuisé son quantum il sera remis (préempté) dans une queue
en attendant d'être choisi à nouveau par le scheduler (répartiteur ou gestionnaire de tâches).
C'est le multi tâche préemptif.
Tous les codes qui tournent le seront dans un thread géré par le système (thread principal).
Mais ces codes peuvent eux aussi lancer des threads.
Les threads sous Android fonctionnent exactement sur le même principe que ceux de JAVA.
Il existe deux solutions pour créer un thread, en créant une sous classe de Handler, ou en
créant une sous-classe de AsyncTask.
Les deux solutions fonctionnent sur le même principe:
- La méthode start() permet de démarrer un thread.
- La méthode run() contient le code qui sera exécuté par ce thread.
- Une méthode sleep() permet de mettre en sommeil un thread.
- Une méthode interrupt() pour interrompre un thread.
UI Thread (User Interface Thread)
L’interface graphique d’Android est conçue pour fonctionner avec un unique thread appelé UI
Thread. Seul ce thread est autorisé à modifier l’IHM. Ce thread est le thread principal de
l'activité courante de l'interface.
Généralement, un thread a une durée de vie très limitée, il doit s'arrêter dès qu'il a fini
d'exécuter sa dernière instruction, mais ce n'est pas le cas pour le UI Thread.
Ce thread est appelé UI thread car il est responsable des interactions avec l'utilisateur.
Toutes les opérations de mises à jour et modifications de l’IHM doivent s’effectuer depuis le
UI Thread.
Pour réaliser ses différents rôles, l'UI thread est implémenté sous forme de boucle infinie.
...
While(true){
si (il y a au moins une chose à faire) alors
{
faire la chose
//Ex1: dessiner à l'écran
//EX2: exécuter le code lié au bouton cliqué
}
}...
Avant de rentrer dans la boucle infinie, ce thread exécute les méthodes onCreate(), onStart(),
onResume(), et lorsque l'Activité se ferme, il sort de la boucle infinie pour ensuite exécuter
les méthodes onPause(), onStop(), onDestroy().
1
Création de threads
Dans une grande majorité de cas, l'UI thread permet de réaliser toutes les opérations
nécessaires au fonctionnement de l'application. Cependant, certaines actions sont
consommatrices en temps.
Dans ce cas, il est intéressant d'envisager la création d'autres threads. Le thread peut, par
exemple, se charger de compléter au fur et à mesure les informations affichées à l'écran. Par
exemple il complète une liste, récupère des informations d'Internet, consulte la base de
données des contacts, etc.
La classe principale de gestion des threads est la classe Thread. Généralement, le thread est
instancié en lui donnant un Runnable qui contient le code qui sera exécuté par le thread.
L'instanciation de l'objet est la première étape, la seconde étape est l'appel de la
méthode start() (cours java).
L’interface Runnable :
public interface Runnable { void run(); }
Implémentation de l’interface pour définir la tâche à exécuter :
public class un_thread implements Runnable {
public void run() {
/* TODO Download */
}}
instanciation et exécution d’un thread :
public void onClick(View v) {
un_thread t= new un_thread();
Thread T = new Thread(t);
T.start();
}
Exemple
public class MainActivity extends Activity {
private ProgressBar bar;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bar = (ProgressBar) findViewById(R.id.progressBar1);
}
public void onclick(View view) {
bar.setProgress(0);
mon_thread th = new mon_thread(bar));
Thread T=new Thread(th);
T.start();
}
class mon_thread implements Runnable {
private ProgressBar bar;
public mon_thread( ProgressBar b){
bar =b;
}
public void run() {
for (int i = 0; i <= 100; i++) {
2
bar.setProgress(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
L'UI thread n'est pas fait pour effectuer des traitements consommateurs en
temps. Tout d'abord, si le traitement excède le délai de 5 secondes nous aurons le
message "application not responsive". Ensuite, en monopolisant le thread on ne le laisse
pas faire son rôle primaire : réaliser les actions de l'utilisateur et modifier l'affichage.
Tous les threads peuvent appeler des méthodes modifiant la vue telles que setText() sur
un TextView, cependant le résultat est garanti uniquement s'il s'agit d'un
appel effectué depuis l'UI thread.
Les taches lourdes
Il faut préserver l'UI thread de tout traitement lourd sous peine de perturber l'affichage ou
les interactions avec l'utilisateur.
Exemple : bloquage de l'UIThread
public class MainActivity extends Activity {
private Button b;
TextView t;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
t= (TextView) findViewById(R.id.textView1);
b = (Button) findViewById(R.id.button1);
}
public void onClick(View v){
try {
Thread.sleep(20000L);
} catch (InterruptedException e) {}
t.setText("affichage après attente dans UI Thread : sequentiel ");
}});
}
Ce n’est qu’au bout de 20000ms que l’affichage se produit !!
3
Solution : toutes les opérations coûteuses en temps d’exécution doivent être placées dans
un autre Thread.
Exemple
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
t= (TextView) findViewById(R.id.textView1);
}
public void onClick(View v){
mon_thread th= new mon_thread();
th.start();
t.setText("Thread : traitement concurent");
}
class mon_thread extends Thread {
public void run() {
try {
Thread.sleep(20000L);
} catch (InterruptedException e) {}
}
Comment agir sur l'interface graphique
L'UI thread ne pouvant exécuter ce type d'actions, il faut créer un thread qui va les réaliser.
Mais il est important de noter qu'un thread ne doit pas agir directement sur l'interface. Ce rôle
doit rester au seul UI Thread, sinon, un message d’erreur.
Exemple : action d'un thread sur l'interface (de l'UIThread)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
t=(TextView)findViewById(R.id.textView1);
b = (Button) findViewById(R.id.button1);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v){
new Thread(new mon_thread(t)).start();
}
});}
public class mon_thread implements Runnable {
private final TextView t;
public mon_thread(TextView tv) {
this.t = tv;
}
public void run() {
t.setText("clic dans un Thread : erreur ");
}}}
Résultat : comportement incertain. Null ne peut remplacer le UI Thread.
Synchronisation avec l'UI Thread
Une façon de faire la Synchronisation avec l'UI Thread est la création d'un autre thread avec
accès prédéfini par envoi de séquence d'affichage ou par envoi de messages ou les deux avec la
classe Asynctask.
Envoi de séquence d'affichage :
- runOnUIThread(Runnable R): Une méthode héritée de la classe activity
4
- unHandler.post(Runnable R) : une instance de la classe Handler
Envoi de messages
- unHandler.send(Message m)
ou le cumul des deux : classe Asynctask
Envoi de séquences d'affichage : runOnUIThread()
Toute portion de code rédigée dans la classe Activity peut communiquer avec l'UI thread par
la méthode runOnUIThread() qui accepte un Runnable en paramètre. Cette méthode permet
de déposer une tâche dans la boucle infinie de l'UI thread.
Exemple
public class TestActivity extends Activity {
Button btn;
int i = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
f();
}}).start();
t.setText("2eme affichage UI Thread");
}
public void f(){
//Déposer le Runnable dans la file d'attente de l'UI thread
/*** t.post(new Runnable() { */
runOnUiThread(new Runnable() {
public void run() {
//code exécuté par l'UI thread
try {
Thread.sleep(500);
t.setText("Thread secondaire ");
} catch (InterruptedException e) {}
}});
}
5
ANR (Application Not Responding) : problème des traitements longs
Les traitements longs (chargement de fichiers, via internet, calculs longs,...), ne pourront pas
être lancés dans le UI thread, car l’ActivityManager contrôle le temps de réponse de ces UI
threads (délai d'attente de moins de 5 secondes). Si l'UI Thread est trop long,
l’ActivityManager proposera à l'utilisateur de le tuer !
D'où parfois ce genre de message s'affiche indiquant que "l'activité en cours ne répond pas,
voulez-vous attendre, ou l'arrêter ? ".
Deux objets dédiés aux threads sont disponibles nativement sur Android, les Handlers et
les AsyncTasks.
Deux possibilités pour communiquer avec le Handler : les messages (de l'objet Message ) et
les objets Runnable.
Les messages
Le Handler est associé à l'activité qui le déclare et travaille au sein du Thread d'IHM.
Alors, tout traitement effectué par le Handler bloque l'IHM le temps qu'il soit effectué. Il
faut considérer le Handler comme celui qui met à jour l'IHM et le Thread appelle le
Handler pour le traitement.
Le Handler ne doit que mettre à jour l'IHM, tout autre comportement est une erreur de
conception.
Un Thread communique avec un Handler au moyen de messages (objets de la classe Message ). Il
s'agit de gérer des messages dans une file d'attente pour un thread.
La file de messages est de type FIFO, le premier message posté sera le premier à être lu.
Un thread crée et envoie de tels messages au handler.
Le Thread récupère l'objet Message du pool du Handler
par handler.obtainedMessage.
Le Thread envoie le message au Handler en utilisant l'une des méthodes
sendMessage().
Le Handler doit surcharger sa méthode handleMessage pour répondre aux
messages qui lui sont envoyés pour mettre à jour l'IHM en fonction de ces
données.
Envoyer des messages
Pour envoyer un message, il faut d'abord créer le corps du message. Pour cela, nous utilisons
une instance de la classe Message à obtenir par la méthode obtain() avec la classe Message
ou obtainMessage() avec la classe Handler afin d’extraire l’objet Message du pool.
Cette méthode est surchargée pour permettre de créer un Message vide ou des messages
contenant des identifiants de messages et des paramètres.
6
Pour envoyer ce message au Handler, on utilise l'une des méthodes de la famille
sendMessage...() :
- sendMessage(Message msg) : place immédiatement le message dans la file.
-sendMessageAtFrontOfQueue(Message msg) : place le message en tête de file.
-sendMessageAtTime(Message msg, long uptimeMillis): place le message dans la file de
messages à l’instant indiqué en paramètre. Cet instant s'exprime en millisecondes par rapport
à l’uptime du système (SystemClock.uptimeMillis()).
-sendMessageDelayed(Message msg, long delayMillis) place le message dans la file de
messages après un certain délai, exprimé en millisecondes.
Avec un message vide: Il ne sera pas nécessaire de passer par handler.obtain() pour obtenir
une instance de message puisque non envoyé.
- sendEmptyMessage(int what): Place un message vide, avec what le contenu du message. Dans ce
cas vous n’avez pas à fournir d’objet Message mais un entier (what) comme seule
donnée de communication.
- sendMessageAtFrontOfQueue(int what) place immédiatement le message en tête de file. à
faire pour des messages prioritaires.
- sendEmptyMessageAtTime(int what,long uptimeMillis): place le message dans la file de
messages à l’instant indiqué en paramètre.
- sendMessageDelayed(int what, long delayMillis) place le message dans la file de messages
après un certain délai, exprimé en millisecondes.
Tous les messages seront reçus dans la méthode "handleMessage(Message msg)" du handler
attaché à un thread.
Pour traiter ces messages, le Handler doit redéfinir la méthode handleMessage(), qui sera
appelée pour chaque message qui apparaît dans la file d’attente.
C’est là que le Handler peut modifier l’interface utilisateur s’il le souhaite. Cependant, il doit
le faire rapidement car les autres opérations de l’interface sont suspendues tant qu’il n’a pas
terminé.
Lecture d'un message
public void handleMessage(Message msg) {
...
}
Cette méthode sera déclenchée à chaque fois qu'il y aura un nouveau message dans la file.
La solution a été implémentée sous cette forme :
Une file de messages, chaque message est un ensemble d’instructions à exécuter.
Des threads peuvent ajouter des messages dans la file.
Seul le UI thread extrait les messages un par un pour les traiter.
Ce thread est dans un mode d’écoute infini dans l’attente d’un nouveau message.
7
Le handler permet d’ajouter des messages à la file de messages.
Les messages transmis par les handlers sont des instances de la classe
Message.
Récupération des messages : obtainMessage
Pour construire un message : Message msg = handler.obtainMessage();
Pour traiter ces messages, le Handler redéfinit la méthode
handleMessage().
Exemple
Gestion d'un message pour faire avancer une barre de progression (créer une ProgressBar et la
modifier via un Handler).
Code java
public class MainActivity extends Activity {
ProgressBar bar;
Handler handler=new Handler() {
//On implémente la méthode handleMessage pour définir le traitement des messages
reçus.
public void handleMessage(Message msg) {
// on incremente la barre de progression de 5
bar.incrementProgressBy(5);
//int value=msg.getData().getInt("cle");
// Incrémenter la ProgressBar, on est bien dans la Thread de l'IHM
// bar.incrementProgressBy(value);
}};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar=(ProgressBar)findViewById(R.id.progressBar1);
}
public void onStart() {
super.onStart();
bar.setProgress(0);
Thread background=new Thread(new Runnable() {
public void run() {
try {
8
for (int i=0;i<20 ;i++) {
Thread.sleep(1000);
Bundle messageBundle = new Bundle();
// Création d’un message vide
Message msg = handler.obtainMessage();
/* messageBundle.putInt("cle",10); //Ajouter des données à transmettre au Handler via
le Bundle
msg.setData(messageBundle); //Ajouter le Bundle au message
*/
// Envoi du message au handler
handler.sendMessage(msg);
//handler.sendMessage(handler.obtainMessage());
}
}
catch (Throwable t) {
// Termine le thread en arrière–plan
}
} //fin run
});
background.start();
}
}
Les objets Runnable
Il est possible de passer dans la file de messages un objet Runnable. Le handler exécutera cet
objet dans l'UI Thread.
Les méthodes post() de Handler, permettent de passer des objets Runnable dans la file
d’attente des événements.
post(Runnable r) : Ajoute un objet Runnable dans la file de messages
final boolean postAtFrontOfQueue(Runnable r) : Ajoute un objet Runnable en
premier dans la file des messages (priorité d'exécution car en premier)
final boolean postAtTime(Runnable r, long uptimeMillis): Ajoute un objet
Runnable dans la file des messages en indiquant quand devra être exécuté celui-ci
final boolean postDelayed(Runnable r, long delayMillis) : Ajoute un
objet Runnable dans la file des messages en indiquant que celui-ci doit être exécuté
après un certain temps.
Exemple
public class MainActivity extends Activity {
private ProgressDialog progressDialog;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
int i = msg.what; //what est le message reçu
switch (i) {
case 1: progressDialog.setMessage("Thread 1 terminé..."); break;
//progressDialog.setProgress(i)
case 2: progressDialog.setMessage("Thread 2 terminé..."); break;
case 3: progressDialog.setMessage("Thread 3 terminé..."); break;
case 4: progressDialog.setMessage("Plus de tutoriels Android"); break;
default:progressDialog.dismiss(); // Fermer le message
}}};
9
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Chargement en cours...");
progressDialog.setTitle("gbarre de … ");
progressDialog.show();
setContentView(R.layout.activity_main);
Thread thread = new Thread(new Runnable() {
public void run() {
try {
// Traitement numéro 1
Thread.sleep(5000);
handler.sendEmptyMessage(1);
// Traitement numéro 2
Thread.sleep(3000);
handler.sendEmptyMessage(2);
// Traitement numéro 3
Thread.sleep(5000);
handler.sendEmptyMessage(3);
// Traitement numéro 4
Thread.sleep(2000);
handler.sendEmptyMessage(4);
// Traitement par défaut
Thread.sleep(10000);
handler.sendEmptyMessage(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}});
thread.start();
}
10
AsyncTask
La classe AsyncTask permet de simplifier l’écriture d’un thread et sa
synchronisation avec le thread principal.
Elle permet le lancement de threads asynchrones en tâche de fond tout en ayant la possibilité
d'informer simplement l'UI Thread de l'état d'avancement.
Ces threads doivent avoir une fin et ne doivent pas tourner en boucle infinie.
Les méthodes
La classe AsyncTask posséde quatre méthodes ( dont une obligatoire doInBackground() )
doInBackground():traitement lancé en arrière plan (donc dans un thread).
onProgressUpdate(): Méthode lancée dans l'UI Thread. Elle permettra d'indiquer
l'avancement du traitement en tâche de fond à l'utilisateur. Cette méthode sera déclenchée
uniquement en utilisant publishProgress() depuis le code de la méthode doInBackground().
onPreExecute():déclenchée depuis l'UI Thread avant le lancement du thread en arrière plan.
Elle pourra par exemple avertir l'utilisateur du lancement du traitement en arrière plan.
onPostExecute():informe de la fin d'un traitement en arrière plan ou pour cacher la progress
bar par exemple.
Remarque
Les trois méthodes onProgressUpdate(), onPreExecute() et onPostExecute() s'exécutent sous l'UI
thread, elles ne doivent pas effectuer de traitement lourd sous peine d'ANR.
Ne jamais lancer la méthode onProgressUpdate directement, mais toujours passer par
publishProgress().
Créer une classe de tâches asynchrones
Cette classe devra étendre la classe AsyncTask :
private class MyTask extends AsyncTask<Params, Progress, Result> { ... }
Les types de paramètres des méthodes
Les signatures des méthodes sont plutôt particulières. Les types de la classe AsyncTask sont
génériques.
Les trois types utilisés par AsyncTask sont les suivants:
Params: Type de paramètres qui seront passés à doInBackground(), afin de définir le typage
des objets sur lesquels on va faire une opération.
Progress: Type de paramètres qui seront passés à onProgressUpdate(), pour indiquer
l'avancement de l'opération.
Result: Type de paramètres qui seront passés à onPostExecute(), utilisé pour symboliser le
résultat de l'opération.
Pour ne passer aucun paramètre, faire :
private class MyTask extends AsyncTask<Void, Void, Void> { }
11
Instanciation la classe AsyncTask
Votre classe devra être chargée, instanciée et exécutée depuis le UI Thread.
Exécuter votre tâche en arrière plan
Pour exécuter une tâche en arrière plan, la méthode execute(Params... params) sera
utilisée.
Interrompre l'exécution d'une tâche
On peut mettre fin à une tâche en arrière plan par la méthode cancel(boolean arg0) (par
exemple, pour traiter la rotation de l'écran qui va détruire l'UI Thread, suite à une demande du
système d'arrêter l'UI Thread en arrière plan par manque de mémoire).
Arg0 : à vrai pour détruire le thread, si le thread n'est pas en cours d'exécution, il ne
s'exécutera jamais.
A faux, le thread ne pourra jamais démarrer, mais il ne s'arrête pas si déjà en cours
d'exécution.
On utilise false si le thread n'est pas conçu pour pouvoir s'interrompre.
Etat de la tâche en arrière plan
La méthode getStatus() retourne l'état de la tâche:
AsyncTask.Status.FINISHED: onPostExecute(Result) a terminé son travail.
AsyncTask.Status.PENDING: le traitement n'a pas encore été démarré
AsyncTask.Status.RUNNING: Le traitement est en cours.
Exemple
public class MainActivity extends Activity {
private ProgressBar monProgressBar;
private Button mButton;
private MaTacheDeFond maTache;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// On récupère les composants de notre layout
monProgressBar = (ProgressBar)
findViewById(R.id.progressBar1);
mButton = (Button) findViewById(R.id.toggleButton1);
maTache=new MaTacheDeFond();
maTache.execute();
// On met un Listener sur le bouton
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
maTache.cancel(true);
}
});
}
class MaTacheDeFond extends AsyncTask<Void, Long, Void> {
protected void onPreExecute() {
Toast.makeText(this, "Lancement de la tâche de fond!",
Toast.LENGTH_LONG).show();
}
12
protected void onProgressUpdate(Long... values){
monProgressBar.setProgress(values[0].intValue());
}
protected Void doInBackground(Void... arg0) {
long progress;
for (progress=0;progress<=100;progress++)
{
if (isCancelled()) break;
try {
publishProgress(progress);
Thread.sleep (1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
protected void onPostExecute(Void result) {
Toast.makeText(getApplicationContext(), "Le traitement est maintenant
terminé", Toast.LENGTH_LONG).show();
}
protected void onCancelled() {
Toast.makeText(getApplicationContext(), "Le traitement
est abandonné", Toast.LENGTH_LONG).show();
}
}
}
13