English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Mécanisme de messages Android
1.Résumé
Lors du démarrage d'une application Android, il existe par défaut un thread principal (UI thread), dans ce thread, il est associé à une file de messages (MessageQueue), toutes les opérations seront encapsulées dans une file de messages et remises au thread principal pour traitement. Pour s'assurer que le thread principal ne quitte pas, les opérations de la file de messages sont placées dans un boucle infinie, le programme semble exécuter en boucle infinie, chaque fois qu'il fait une boucle, il tire un message de l'intérieur de la file de messages, puis appelle la fonction de traitement du message correspondante (handlerMessage), après l'exécution d'un message, il continue de boucler, si la file de messages est vide, le thread sera bloqué en attendant. Par conséquent, il ne quitte pas. Comme le montre l'image suivante :
Quelle est la relation entre Handler, Looper et Message?
Pour terminer des opérations longues dans un sous-thread, dans de nombreux cas, il est nécessaire de mettre à jour l'UI, ce qui est le plus couramment fait par le biais d'un Handler pour poster un message dans le thread UI, puis le traiter dans la méthode handlerMessage du Handler. Chaque Handler est associé à une file de messages (MessageQueue), et Looper est responsable de créer une file de messages, et chaque Looper est associé à un thread (Looper est encapsulé par ThreadLocal). Par défaut, la file de messages MessageQueue n'a qu'une seule, c'est-à-dire la file de messages du thread principal.
C'est là les principes fondamentaux du mécanisme de messages d'Android. Si vous voulez en savoir plus, nous allons commencer par le code source.
2.Analyse du code source
(1)Lancer la boucle de messages Looper dans le thread principal ActivityThread
public final class ActivityThread { public static void main(String[] args) { //Code omis //1.Créer le Looper de la boucle de messages Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); //2.Exécuter la boucle de messages Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
ActivityThread crée la file de messages du thread principal par la méthode Looper.prepareMainLooper(), puis exécute Looper.loop() pour démarrer la file de messages. Handler lie la file de messages et le thread.
(2)Handler lie la file de messages et le thread
public Handler(Callback callback, boolean async) {}} //Code omis //Obtenir Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare() } //Obtenir la file de messages mQueue = mLooper.mQueue; }
Handler utilise l'objet Looper obtenu par la méthode Looper.getLooper() à l'intérieur pour s'associer et obtenir la file de messages. Alors, comment fonctionne Looper.getLooper()?
public static @Nullable Looper myLooper() { retourner sThreadLocal.get(); } public static @NonNull MessageQueue myQueue() { retourner myLooper().mQueue; } public static void prepare() { prepare(true); } //Définir un Looper pour le thread courant private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { lancer une nouvelle RuntimeException("Un seul Looper peut être créé par thread"); } sThreadLocal.set(new Looper(quitAllowed)); } //Définir le Looper du thread UI public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { lancer une nouvelle IllegalStateException("Le main Looper a déjà été préparé."); } sMainLooper = myLooper(); } }
Dans la classe Looper, la méthode myLooper(), est obtenue par sThreadLocal.get(), appelée dans prepareMainLooper(), où la méthode prepare() est appelée. Dans cette méthode, un objet Looper est créé et l'objet est configuré en sThreadLocal(). De cette manière, la file est associée au thread. La méthode sThreadLocal.get() garantit que différents threads ne peuvent pas accéder à la file de messages de l'autre.
Pourquoi doit-on créer le Handler pour la mise à jour de l'UI sur le thread principal ?
Parce que le Handler doit être associé à la file de messages du thread principal, de sorte que handlerMessage puisse être exécuté sur le thread UI, ce qui rend le thread UI sécurisé à ce moment-là.
(3)Message loop, message processing
The establishment of the message loop is through the Looper.loop() method. The source code is as follows:
/** * Run the message queue in this thread. Be sure to call * Use {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //1.Get the message queue final MessageQueue queue = me.mQueue; //2.Dead loop, i.e., message loop for (;;) { //3.Get the message, may block Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } //4.Process the message msg.target.dispatchMessage(msg); //Recycle the message msg.recycleUnchecked(); } }
From the above program, we can see that the essence of the loop() method is to establish a dead loop, then extract messages one by one from the message queue, and finally process the messages. For Looper: create a Looper object (the message queue is encapsulated in the Looper object) through Looper.prepare(), and then keep it in sThreadLocal, and then loop the message through Looper.loop(). These two steps usually appear together.
public final class Message implements Parcelable { //Target processing Handler target; //Runnable type callback Runnable callback; //The next message, the message queue is stored in a linked manner Message next; }
On the source code, we can see that 'target' is of Handler type. In fact, it just goes around, sending messages to the message queue through Handler, and the message queue then distributes the messages to Handler for processing. In the Handle class:
//Fonction de traitement des messages, redéfinie par la sous-classe public void handleMessage(Message msg) { } private static void handleCallback(Message message) { message.callback.run(); } //Distribution des messages public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
On peut voir à partir du programme ci-dessus que dispatchMessage n'est qu'une méthode de distribution, si le callback de type Runnable est vide, alors execute handleMessage pour traiter le message, cette méthode est vide, nous écrirons le code de mise à jour de l'UI dans cette fonction ; si le callback n'est pas vide, alors execute handleCallback pour traiter, cette méthode appelle la méthode run du callback. En réalité, c'est deux types de distribution de Handler, par exemple, si post(Runnable callback) est vide, alors nous ne configurons généralement pas le callback lorsque nous utilisons sendMessage avec Handler, donc execute handleMessage.
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public String getMessageName(Message message) { if (message.callback != null) { return message.callback.getClass().getName(); } return "0x" + Integer.toHexString(message.what); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + "sendMessageAtTime() appelé sans mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
On peut voir dans le programme ci-dessus que lors de l'appel de post(Runnable r), le Runnable est enveloppé dans un objet Message, et l'objet Runnable est configuré comme callback de l'objet Message, puis l'objet est inséré dans la file de messages. L'implémentation de sendMessage est similaire :
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
Que ce soit pour poster un Runnable ou un Message, sendMessageDelayed(msg, time) est appelé. Le Handler ajoute finalement le message à la MessageQueue, et le Looper lit constamment les messages de la MessageQueue et appelle la méthode dispatchMessage du Handler pour distribuer les messages, de sorte que les messages sont produits, ajoutés à la MessageQueue et traités par le Handler en continu, permettant ainsi au système d'application Android de fonctionner.
3.Vérification
new Thread(){ Handler handler = null; public void run () { handler = new Handler(); ; .start();
Y a-t-il un problème dans le code ci-dessus ?
L'objet Looper est de type ThreadLocal, ce qui signifie que chaque thread utilise son propre Looper, ce Looper peut être vide. Cependant, lorsqu'on crée un objet Handler dans un sous-thread, si le Looper est vide, une exception se produira.
public Handler(Callback callback, boolean async) {}} //Code omis //Obtenir Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare() } //Obtenir la file de messages mQueue = mLooper.mQueue; }
Lorsque mLooper est vide, il lève une exception. Cela est dû au fait que l'objet Looper n'a pas été créé, donc sThreadLocal.get() renverra null. Le principe de base d'un Handler est de s'établir une connexion avec MessageQueue et de déposer des messages dans MessageQueue. Sans MessageQueue, un Handler n'a pas d'utilité, et MessageQueue est enfermé dans Looper, donc lors de la création d'un Handler, Looper ne peut pas être vide. La solution suivante est proposée :
new Thread(){ Handler handler = null; public void run () { //Créer un Looper pour le thread courant et le lier à ThreadLocal Looper.prepare(); handler = new Handler(); //Démarrer le cycle des messages Looper.loop(); ; .start();
Si vous créez un Looper sans démarrer le cycle des messages, bien que cela ne provoque pas d'exception, poster ou envoyer un message() via le handler ne sera pas efficace. Car bien que le message soit ajouté à la file de messages, le cycle des messages n'a pas été démarré, donc il ne récupérera pas de messages de la file de messages et ne les exécutera pas.
Merci de lire, j'espère que cela pourra aider tout le monde, merci de votre soutien à ce site !