Lors du développement d'applications Android, la gestion des threads d'arrière-plan est essentielle pour éviter de bloquer l'interface utilisateur. Le mécanisme de Handler permet de communiquer entre threads, et HandlerThread fournit une solusion intégrée pour exécuter des tâches chronophages dans un thread séparé avec sa propre boucle de messages. Dans cet article, nous explorons l'implémentation de HandlerThread et son utilisation pratique.
Utilisation de base dans une Activiyt Android
Voici un exemple modifié de comment intégrer HandlerThread dans une Activity. Les noms de variables et la structure ont été adaptés pour réduire la similarité avec l'original.
public class SampleActivity extends AppCompatActivity {
private HandlerThread backgroundThread;
private Handler taskHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
// Créer et démarrer le thread d'arrière-plan
backgroundThread = new HandlerThread("DataProcessor");
backgroundThread.start();
// Initialiser le handler avec le looper du thread
taskHandler = new Handler(backgroundThread.getLooper()) {
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
if (message.what == 200) {
Log.i("ThreadDemo", "Nom du thread : " + Thread.currentThread().getName());
Log.i("ThreadDemo", "Contenu du message : " + message.getData().getString("content"));
}
}
};
// Configurer l'écouteur d'événement
findViewById(R.id.btn_send).setOnClickListener(view -> {
Log.i("ThreadDemo", "Thread principal : " + Thread.currentThread().getName());
Message msg = taskHandler.obtainMessage();
msg.what = 200;
Bundle data = new Bundle();
data.putString("content", "exemple de données");
msg.setData(data);
taskHandler.sendMessage(msg);
});
}
}
Lorsqu'on clique sur le bouton, le message est envoyé depuis le thread principal et traité dans le thread d'arrière-plan, comme le montrent les journaux ci-dessous :
I/ThreadDemo: Thread principal : main
I/ThreadDemo: Nom du thread : DataProcessor
I/ThreadDemo: Contenu du message : exemple de données
Cette approche est utile pour des tâches comme l'écriture de journaux d'événements dans un fichier, où les messages sont traités de manière ordonnée sans affecter la réactivité de l'interface.
Analyse approfondie du code source
La classe HandlerThread étend Thread et encapsule la création d'un Looper et la gestion de la boucle de messages. Examinons sa srtucture interne.
public class HandlerThread extends Thread {
private int priorityLevel;
private int threadIdentifier = -1;
private Looper messageLooper;
private @Nullable Handler internalHandler;
// Le constructeur initialise le nom et la priorité du thread
public HandlerThread(String threadName) {
super(threadName);
priorityLevel = Process.THREAD_PRIORITY_DEFAULT;
}
// Autres méthodes...
}
Lorsque start() est appelé, la méthode run() est exécutée. Voici une version modifiée de la méthode run pour illustrer le processus :
@Override
public void run() {
// Enregistrer l'identifiant du thread courant
threadIdentifier = Process.myTid();
// Préparer le looper pour ce thread
Looper.prepare();
// Synchronisation pour garantir l'accès sécurisé au looper
synchronized (this) {
messageLooper = Looper.myLooper();
notifyAll(); // Réveiller les threads en attente
}
// Appliquer la priorité configurée
Process.setThreadPriority(priorityLevel);
// Appel de callback avant le démarrage de la boucle
onLooperPrepared();
// Démarrer la boucle de messages
Looper.loop();
// Réinitialiser l'identifiant après la fin du thread
threadIdentifier = -1;
}
La méthode getLooper() permet d'obtenir l'instance du Looper associée au thread. Elle utilise une attente active pour s'assurer que le looper est initialisé :
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && messageLooper == null) {
try {
wait(); // Attendre la création du looper
} catch (InterruptedException e) {
// Ignorer l'interruption
}
}
}
return messageLooper;
}
Une méthode cachée (@hide) getThreadHandler() existe pour récupérer directement un Handler, mais elle n'est pas accessible publiquement. Cela pourrait être dû à la nécessité de démarrer le thread avant d'accéder au Handler. Par conséquent, il est recommandé d'utiliser la séquence standard : démarrer le thread, obtenir le looper, puis créer le Handler.
Pour arrêter proprement le thread, la méthode quit() est disponible :
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit(); // Arrêter la boucle de messages
return true;
}
return false;
}
HandlerThread simplifie la gestion des threads avec une boucle de messages intégrée, idéale pour les tâches asynchrones répétitives comme le traitement de données ou la journalisation.