L’objectif est de programmer des tâches d’arrière-plan qui interagissent avec l’IHM (thread UI).
Ici, nous allons tout simplement afficher la date et l’heure en proposant plusieurs solutions à base de Timer/TimerTask, de threads ou AsyncTask.
Toute application Android est composée d’une multitude de threads.
Le thread UI (User Interface) est le fil d’exécution d’une activité. Il est responsable de l’affichage et des interactions avec l’utilisateur et surtout c’est le seul thread qui doit modifier l’affichage.
Remarque : on ne peut pas effectuer des traitements consommateurs en temps dans le thread UI car celui-ci se “figerait” et ne répondrait plus aux actions de l’utilisateur. D’autre part si une activité réagit en plus de cinq secondes, elle sera tuée par l’ActivityManager qui la considérera comme morte.
Il faudra donc créer et exécuter des threads d’arrière-plan pour les traitements lourds.
En Java, il y a plusieurs façons de créer et exécuter un thread. Le principe de base revient à dériver (extends
) une classe Thread
ou à implémenter (implements
) l’interface Runnable
et à écrire le code du thread dans la méthode run()
. Ensuite, on appelera la méthode start()
pour démarrer le thread et la méthode stop()
pour l’arrêter.
Runnable
:// Méthode 1 : écriture d'une classe
// Une classe "Thread"
public class UnThread implements Runnable
{
// Le code du thread
public void run()
{
}
}
// Instanciation
UnThread unThread = new UnThread();
Thread leThread = new Thread(unThread);
// Démarrage
leThread.start(); // -> appel run()
On utilise plus souvent les classes anonymes (on ne lui donne pas de nom et son implémentation suit immédiatement sa déclaration) :
// Méthode 2 :
Thread leThread = new Thread(new Runnable() {
// Le code du thread
public void run() {
}
});
// Démarrage
leThread.start(); // -> appel run()
Thread
:class UnThread extends Thread {
// Le code du thread
public void run() {
}
}
// Instanciation
UnThread leThread = new UnThread();
// Démarrage
leThread.start(); // -> appel run()
Documentation Android : Thread
La classe Timer
permet de programmer des tâches pour une exécution dans un thread d’arrière-plan. Les tâches peuvent être programmées pour une exécution unique (single shot) ou pour une exécution répétée à intervalles réguliers. La classe Timer
fournit donc un minuteur ou temporisateur.
La programmation du timer se fait par la méthode schedule(TimerTask task, long delay, long period)
en lui indiquant en paramétres :
task
: la tâche à exécuter qui est un objet de type TimerTask
delay
: le délai en ms après lequel la tâche sera exécutéeperiod
: la période en ms de répétition de l’exécution de la tâcheSi on ne précise pas le paramétre period
, la tâche sera programmée pour un exécution unique.
La classe Timer
fournit aussi la méthode cancel()
pour terminer le timer.
Documentation Android : Timer
La classe TimerTask
permet de créer une tâche qui peut être planifiée pour une exécution unique (single shot) ou répétée par un Timer
. Le code de la tâche s’implémentera par la redéfinition de sa méthode run()
.
La classe TimerTask
fournit les méthodes cancel()
pour annuler la tâche et scheduledExecutionTime()
qui retourne le temps d’exécution.
Documentation Android : TimerTask
Depuis la version 1.5 d’Android, la classe AsyncTask
permet une nouvelle façon d’effectuer des tâches en arrière-plan.
Elle s’exécutera dans deux threads distincts : le thread UI et le thread de traitement.
Les méthodes à redéfinir de la classe AsynchTask
sont :
doInBackground()
est la méthode qui s’exécute dans un autre thread. Seule cette méthode est exécutée dans un thread à part, les autres méthodes s’exécutent dans le thread UI.
onPreExecute()
est appelée par le thread UI avant l’appel à doInBackground()
.
onPostExecute()
est appelée lorsque la méthode doInBackground()
est terminée.
onProgressUpdate()
est appelée par la méthode publishProgress()
à l’intérieur de la méthode doInBackground()
.
L’idée est que si on fait un traitement lourd qui n’a besoin que de donner son avancement et sa fin (typiquement charger des données), le mieux est l’utilisation de l’AsyncTask
.
Documentation Android : AsyncTask
Si la tâche doit interagir avec l’IHM, on pourra utiliser runOnUiThread()
pour exécuter le code dans le thread UI.
public class MainActivity extends Activity
{
Button btnStart, btnCancel;
TextView textView;
Timer timer;
TimerTask myTimerTask;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)findViewById(R.id.start);
btnCancel = (Button)findViewById(R.id.cancel);
textView = (TextView)findViewById(R.id.counter);
btnStart.setOnClickListener(new OnClickListener(){
public void onClick(View arg0) {
if(timer != null){
timer.cancel();
}
timer = new Timer();
doTask();
}
});
btnCancel.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
if (timer!=null){
timer.cancel();
timer = null;
}
}
});
}
public void doTask() {
myTimerTask = new TimerTask() {
public void run() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
final String strDate = simpleDateFormat.format(calendar.getTime());
runOnUiThread(new Runnable() {
public void run() {
textView.setText(strDate);
}
});
}
};
timer.schedule(myTimerTask, 1000, 1000);
}
}
Runnable
Si la tâche doit interagir avec l’IHM, on pourra utiliser la classe Handler
pour communiquer avec celle-ci.
L’Handler
est associé à l’activité qui le déclare et travaille au sein du thread UI. Le handler se chargera de mettre à jour l’IHM. Le thread peut communiquer avec cet handler au moyen de messages ou des objets Runnable
.
public class MainActivity extends Activity
{
//...
final Handler handler = new Handler();
//...
public void doTask(){
myTimerTask = new TimerTask() {
public void run() {
handler.post(new Runnable() {
public void run() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
final String strDate = simpleDateFormat.format(calendar.getTime());
textView.setText(strDate);
}
});
}
};
timer.schedule(myTimerTask, 1000, 1000);
}
}
Si la tâche doit interagir avec l’IHM, on pourra utiliser la classe Handler
pour communiquer avec celle-ci.
L’Handler
est associé à l’activité qui le déclare et travaille au sein du thread UI. Le handler se chargera de mettre à jour l’IHM. Le thread peut communiquer avec cet handler au moyen de messages ou des objets Runnable
.
Pour cela :
le thread récupère un objet Message
du pool du handler par la méthode Message.obtain()
. Il peut ensuite ajouter un code (what
) et d’autre(s) paramètre(s) (arg1, arg2 ou obj
) ;
le thread envoie le message au handler en utilisant la méthode sendMessage()
qui envoie le message et le place à la fin de la queue,
le handler doit surcharger sa méthode handleMessage()
pour traiter les messages qui lui sont envoyés.
On modifie l’activité :
public class MainActivity extends Activity
{
//...
final private Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
super.handleMessage(msg);
if(msg.what == 1)
{
// Modifie l'IHM
textView.setText((String)msg.obj);
}
...
}
};
//...
public void doTask() {
myTimerTask = new TimerTask() {
public void run() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
final String strDate = simpleDateFormat.format(calendar.getTime());
Message msg = Message.obtain();
msg.what = 1;
msg.obj = strDate;
handler.sendMessage(msg);
}
};
timer.schedule(myTimerTask, 1000, 1000);
}
}
Si la tâche doit interagir avec l’IHM, on pourra utiliser un simple thread d’arrière plan et utiliser la classe Handler
comme vu dans la solution n°3.
Ici on n’utile plus le timer mais un simple sleep()
en millisecondes fourni par la classe Thread
.
On modifie l’activité :
public class MainActivity extends Activity
{
//...
public void doTask() {
Thread leThread = new Thread(new Runnable() {
public void run() {
String strDate;
while(true)
{
try
{
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss a");
strDate = simpleDateFormat.format(calendar.getTime());
Message msg = Message.obtain();
msg.what = 1;
msg.obj = strDate;
handler.sendMessage(msg);
Thread.sleep(1000);
}
catch (Exception e)
{
e.getLocalizedMessage();
}
}
}
});
// Démarrage
leThread.start(); // -> appel run()
}
}
La classe AsyncTask
est générique et attend trois paramètres AsyncTask<Type1, Type2, Type3>
où :
doInBackground()
)onProgressUpdate()
)onPostExecute()
) et qui est donc la valeur de retour de doInBackground()
Attention : l’exemple d’afficher la date et l’heure n’est pas en soi adapté à l’utilisation d’un AsyncTask
!
public class MainActivity extends Activity
{
//...
public void doTask() {
// Lancement de l'AsyncTask
new MyAsyncTask().execute();
}
class MyAsyncTask extends AsyncTask<Void, String, Boolean>
{
protected Boolean doInBackground(Void... unused) {
try {
while (true) {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss a");
String strDate = simpleDateFormat.format(calendar.getTime());
Thread.sleep(1000);
publishProgress(strDate);
}
}
catch (InterruptedException t)
{
return false;
}
}
protected void onProgressUpdate(String... msg)
{
textView.setText(msg[0]);
}
protected void onPostExecute(Boolean etat)
{
if(etat)
Toast.makeText(getApplicationContext(), "Terminé avec succes", Toast.LENGTH_SHORT).show();
}
}
}