La route vers l'apprentissage Android (13) Explication détaillée de Handler

1. Introduction

Handler est un ensemble de mécanismes de transmission de messages Android, principalement utilisés pour la communication inter-thread.

Décrivez-le dans les termes les plus simples : le gestionnaire est en fait un sous-thread créé par le thread principal. Le sous-thread s'exécute et génère des messages. Le Looper obtient le message et le transmet au gestionnaire. Le gestionnaire obtient les messages dans le sous-thread. -les fils un par un.

Binder/Socket est utilisé pour la communication inter-processus, tandis que le mécanisme de message Handler est utilisé pour la communication inter-thread dans le même processus.

On peut dire que partout où il existe un thread asynchrone communiquant avec le thread principal, il doit y avoir un gestionnaire.

Dans les scénarios d'application multithread, les informations d'opération qui doivent être mises à jour dans le thread de travail sont transférées vers le thread principal de l'interface utilisateur, afin que le thread de travail puisse mettre à jour l'interface utilisateur et enfin réaliser le traitement des messages asynchrones.

L'objectif principal de l'utilisation du mécanisme de transmission de messages Handler est de garantir la sécurité des threads lors de la mise à jour de l'interface utilisateur simultanément avec plusieurs threads.

2. Explication des concepts associés

Gestionnaire, message, file d'attente de messages, boucleur

  • Message : représente un comportement ou une série d'actions exécutables. Chaque message a un gestionnaire cible clair lorsqu'il est ajouté à la file d'attente des messages.
  • ThreadLocal : Thread Local Storage (TLS en abrégé). Chaque thread possède sa propre zone de stockage locale privée. Différents threads ne peuvent pas accéder à la zone TLS des autres. La fonction de ThreadLocal est de fournir des variables locales TLS au sein du thread. Cette variable fonctionne dans le cycle de vie du thread. Chaque thread a sa propre valeur (isolation des threads).
  • MessageQueue (implémenté à la fois dans la couche C et dans la couche Java) : Fournit un travail d'insertion et de suppression sous la forme d'une file d'attente. Sa structure interne stocke les messages sous la forme d'une liste doublement chaînée.
  • Looper (implémenté à la fois dans la couche C et dans la couche Java) : Looper signifie boucle. Il est responsable de la récupération cyclique des messages de la file d'attente des messages, puis de leur transmission au gestionnaire pour traitement. Gestionnaire : le véritable processeur du message, avec la capacité de obtenir des messages, envoyer des messages
    , traiter les messages, supprimer des messages et autres fonctions

Mécanisme de messagerie Android :

  • En prenant la méthode sendMessage de Handler comme exemple, après l'envoi d'un message, le message sera ajouté à la file d'attente des messages MessageQueue.
  • Looper est chargé de parcourir la file d'attente des messages et de distribuer les messages dans la file d'attente au gestionnaire correspondant pour traitement.
  • Le message est traité dans la méthode handleMessage du gestionnaire, qui termine l'envoi et le traitement d'un message.

Schéma du gestionnaire :

Modèle de mécanisme de message :

  • Message : le message qui doit être transmis, les données peuvent être transmises ;
  • MessageQueue : file d'attente de messages, mais son implémentation interne n'utilise pas de file d'attente. Elle maintient en fait la liste de messages via une structure de données de liste à chaînage unique, car les listes à chaînage unique présentent des avantages en matière d'insertion et de suppression. Les fonctions principales sont de transmettre des messages au pool de messages (MessageQueue.enqueueMessage) et de retirer les messages du pool de messages (MessageQueue.next) ;
  • Handler : classe auxiliaire de message, sa fonction principale est d'envoyer divers événements de message au pool de messages (Handler.sendMessage) et de traiter les événements de message correspondants (Handler.handleMessage) ;
  • Looper : s'exécute en continu en boucle (Looper.loop), lit les messages de MessageQueue et distribue les messages au processeur cible selon le mécanisme de distribution.

Architecture du mécanisme de message

  • Une fois que le thread enfant a terminé l'opération fastidieuse, lorsque le gestionnaire envoie le message, MessageQueue.enqueueMessage sera appelé pour ajouter le message à la file d'attente des messages.
  • Lorsque la boucle est démarrée via Looper.loop, les messages seront lus en continu à partir du pool de threads, c'est-à-dire que MessageQueue.next est appelé.
  • Appelez ensuite la méthode dispatchMessage du gestionnaire cible (c'est-à-dire le gestionnaire qui a envoyé le message) pour remettre le message, puis revenez au thread où se trouve le gestionnaire. Le gestionnaire cible reçoit le message, appelle la méthode handleMessage, reçoit le message et traite le message.

3. Utilisation de base de Handler

3.1 Créer un gestionnaire

Le gestionnaire nous permet d'envoyer des messages retardés. Si l'utilisateur ferme l'activité pendant le délai, l'activité sera divulguée.

Cette fuite est due au fait que Message contiendra le gestionnaire et qu'en raison des caractéristiques de Java, la classe interne contiendra la classe externe, de sorte que l'activité sera détenue par le gestionnaire, ce qui conduira finalement à la fuite d'activité.

Solution : définissez Handler en tant que classe interne statique, conservez une référence faible à l'activité en interne et supprimez tous les messages en temps opportun.

public class HandlerActivity extends AppCompatActivity {

    private Button bt_handler_send;

    private static class MyHandler extends Handler {

        //弱引用持有HandlerActivity , GC 回收时会被回收掉
        private WeakReference<HandlerActivity> weakReference;

        public MyHandler(HandlerActivity activity) {
            this.weakReference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = weakReference.get();
            super.handleMessage(msg);
            if (null != activity) {
                //执行业务逻辑
                Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.HandlerActivity);

        //创建 Handler
        final MyHandler handler = new MyHandler(this);

        bt_handler_send = findViewById(R.id.bt_handler_send);
        bt_handler_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //使用 handler 发送空消息
                        handler.sendEmptyMessage(0);

                    }
                }).start();
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        //移除所有回调及消息
        myHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

3.2 Acquisition des messages

Il existe probablement plusieurs façons d'obtenir un message :

Message message = myHandler.obtainMessage(); 		   //通过 Handler 实例获取
Message message1 = Message.obtain();   			      //通过 Message 获取
Message message2 = new Message();      				 //直接创建新的 Message 实例

En regardant le code source, nous pouvons voir que la méthode getMessage() de Handler appelle également la méthode get() de Message.

public final Message obtainMessage()
{
    return Message.obtain(this);
}

En visualisant la méthode d'obtention de Message

public static Message obtain(Handler h) {
        //调用下面的方法获取 Message
        Message m = obtain();
        //将当前 Handler 指定给 message 的 target ,用来区分是哪个 Handler 的消息
        m.target = h;

        return m;
    }
    
//从消息池中拿取 Message,如果有则返回,否则创建新的 Message
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

Afin de réduire les coûts, nous essayons de réutiliser Message lors de son utilisation et utilisons les deux premières méthodes pour le créer.

3.3 Le gestionnaire envoie un message

Handler fournit une série de méthodes pour nous permettre d'envoyer des messages, telles que les séries send() et post(). La méthode post doit transmettre un objet Runnalbe. Jetons un coup d'œil au code source de la méthode post.

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

Mais quelle que soit la méthode que nous appelons, nous finirons par atteindre la méthode MessageQueue.enqueueMessage(Message,long).
Prenons l'exemple de la méthode sendEmptyMessage(int) :

//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
  			-> queue.enqueueMessage(Message, long);

À partir de là, vous pouvez trouver la file d'attente de messages MessageQueue, qui est responsable de l'entrée et de la sortie des messages dans la file d'attente.

4. Deux instances (thread principal - thread enfant)

4.1 Sous-thread vers le thread principal

Nous ajoutons d’abord une classe interne statique à MainActivity et remplaçons sa méthode handleMessage.

private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mTarget;

        public MyHandler(MainActivity activity) {
            mTarget = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = weakReference.get();
            super.handleMessage(msg);
            if (null != activity) {
                //执行业务逻辑
                if (msg.what == 0) {
                	Log.e("myhandler", "change textview");
                	MainActivity ma = mTarget.get();
	                ma.textView.setText("hahah");
            	}
                Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
            }
        
        }
 }

Créez ensuite une propriété privée de type MyHandler :

private Handler handler1 = new MyHandler(this);

Enfin, créez un fil de discussion dans le rappel onCreate pour recevoir et envoyer des messages :

new Thread(new Runnable() {
            @Override
            public void run() {
                handler1.sendEmptyMessage(0);
            }
        }).start();

en conclusion:

  • Les objets de sous-classe de gestionnaire sont généralement créés dans le thread principal afin qu'ils soient accessibles dans les deux threads. Nous avons créé une sous-classe de la classe Handler, MyHandler, et remplacé la méthode handlerMessage. Cette méthode est utilisée pour recevoir et traiter les messages envoyés. Ensuite, nous avons créé un thread enfant, dans lequel nous avons utilisé l'objet MyHandler pour appeler la méthode sendEmptyMessage afin d'envoyer un message vide. Ensuite, nous pouvons recevoir ces données dans le thread principal.

4.2 Du fil principal au fil enfant

Créez d’abord une classe MyHandler.

private static class MyHandler extends Handler {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Log.e("child thread", "receive msg from main thread");
            }
        }
    }

Déclarez une variable privée de type Handler et initialisez-la à null par défaut.

private Handler handler1;

Créez un thread enfant et faites pointer le gestionnaire vers l'objet MyHandler nouvellement créé.

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler1 = new MyHandler();
                Looper.loop();
                Log.e("child thread", "child thread end");
            }
        }).start();

Envoyer un message du fil principal au fil enfant

while (handler1 == null) {

        }

        handler1.sendEmptyMessage(0);
        handler1.getLooper().quitSafely();

5. Principe du mécanisme du gestionnaire

5.1 Présentation du principe du gestionnaire

Les threads ordinaires n'ont pas de boucleurs. Si un objet boucleur est nécessaire, la méthode Looper.prepare doit être appelée en premier, et un thread ne peut avoir qu'un seul boucleur.

Comment Handler réalise-t-il la communication entre threads ?

  • Android utilise la communication par canal sous Linux
  • Concernant les pipes, pour faire simple, un pipe est un fichier
  • Aux deux extrémités du tube, il y a deux descripteurs de fichiers ouverts. Ces deux descripteurs de fichiers ouverts correspondent au même fichier. L'un d'eux est utilisé pour la lecture et l'autre pour l'écriture.
  • Lorsque la file d'attente des messages est créée
  • Appelez la fonction JNI pour initialiser l'objet NativeMessageQueue. NativeMessageQueue initialisera l'objet Looper
  • La fonction de Looper est de faire passer le thread principal de l'application Android en état d'attente lorsqu'il n'y a aucun message dans la file d'attente des messages de la couche Java. Lorsqu'un nouveau message arrive dans la file d'attente des messages de la couche Java, il réveille le fil principal de l'application Android pour gérer cette actualité

  • Le gestionnaire envoie le message à la file d'attente MessageQueue via sendMessage()
  • Looper extrait en continu les messages qui répondent aux conditions de déclenchement via loop() et transmet les messages à la cible pour traitement.
  • Après dispatchMessage(), il est renvoyé au handleMessage() du gestionnaire pour le traitement correspondant.
  • Lors de l'ajout d'un message à MessageQueue, l'écriture de caractères dans le tube peut réveiller le fil de boucle ; s'il n'y a pas de message dans MessageQueue et qu'il est à l'état inactif, les méthodes de l'interface IdelHandler seront exécutées, ce qui est souvent utilisé pour faire quelques travaux de nettoyage.

5.2 Communication entre le gestionnaire et le pipeline

Les tuyaux sont essentiellement des fichiers, mais ils sont différents des fichiers ordinaires : la taille du tampon du tuyau est généralement de 1 page, soit 4 Ko. Le pipeline est divisé en une extrémité de lecture et une extrémité d'écriture. L'extrémité de lecture est chargée d'obtenir les données du canal et de les bloquer lorsque les données sont vides. L'extrémité d'écriture écrit les données dans le canal et les bloque lorsque le tampon du canal est plein.

  • Dans le mécanisme Handler, la méthode Looper.loop traitera en continu le message dans une boucle, où le message est obtenu via la méthode Message msg = queue.next(); pour obtenir le message suivant. Cette méthode appellera la méthode nativePollOnce(), qui est une méthode native, puis entrera dans la couche native via un appel JNI. Le mécanisme de pipeline est utilisé dans le code de la couche native.
  • Nous savons que la mémoire est partagée entre les threads. Grâce à la communication avec le gestionnaire, le contenu du pool de messages n'a pas besoin d'être copié d'un thread à un autre, car la mémoire disponible pour les deux threads est la même zone et les deux ont le droit d'accéder directement Bien sûr, il existe également des threads Zone privée ThreadLocal (non abordés ici). Puisqu’il n’est pas nécessaire de copier de la mémoire, quel est le rôle du pipeline ?
  • Le rôle du pipeline dans le mécanisme du gestionnaire est que lorsqu'un thread A prépare un message et le place dans le pool de messages, il doit notifier un autre thread B pour traiter le message. Le thread A écrit les données 1 à l'extrémité d'écriture du tube (pour les anciennes versions d'Android, le caractère W est écrit). Lorsque le tube contient des données, il réveillera le thread B pour traiter le message. La tâche principale du pipeline est de notifier un autre thread, qui est la fonction principale.

Pourquoi utiliser Pipeline au lieu de Binder ?

  • Du point de vue de la mémoire : Binder implique également une copie de la mémoire pendant le processus de communication. Le message dans le mécanisme de gestionnaire n'a pas du tout besoin d'être copié, il se trouve dans la même mémoire. Tout ce dont le gestionnaire a besoin est d'indiquer à un autre thread que les données sont disponibles.
  • Du point de vue du processeur, le pilote sous-jacent pour la communication Binder a également besoin d'un pool de threads de liaison. Chaque communication implique la création de threads de liaison et l'allocation de mémoire, ce qui constitue un gaspillage de ressources CPU.

5.3 Explication détaillée des exemples de gestionnaires

Handler est l’interface de couche supérieure du mécanisme de messagerie Android. Le processus d'utilisation de Handler est très simple. Il peut facilement basculer une tâche vers le thread où se trouve le Handler pour son exécution. Habituellement, le scénario d'utilisation de Handler consiste à mettre à jour l'interface utilisateur

Voici un exemple simple d'utilisation du mécanisme de message :

public class Activity extends android.app.Activity {
	private Handler mHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			System.out.println(msg.what);
		}
	};
	@Override
	public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
		super.onCreate(savedInstanceState, persistentState);
		setContentView(R.layout.activity_main);
		new Thread(new Runnable() {
			@Override
			public void run() {
				...............耗时操作
				Message message = Message.obtain();
				message.what = 1;
				mHandler.sendMessage(message);
			}
		}).start();
	}
}

Autre exemple : Comment envoyer un message du fil principal vers le fil enfant ? (Bien que ce scénario d'application soit rare)

Thread thread = new Thread(){
            @Override
            public void run() {
                super.run();
                //初始化Looper,一定要写在Handler初始化之前
                Looper.prepare();
                //在子线程内部初始化handler即可,发送消息的代码可在主线程任意地方发送
                handler=new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                      //所有的事情处理完成后要退出looper,即终止Looper循环
                        //这两个方法都可以,有关这两个方法的区别自行寻找答案
                        handler.getLooper().quit();
                        handler.getLooper().quitSafely();
                    }
                };
              
                //启动Looper循环,否则Handler无法收到消息
                Looper.loop();
            }
        };
        thread.start();
    //在主线程中发送消息
    handler.sendMessage();

Expliquons d'abord la première ligne de code Looper.prepare();, regardons d'abord la méthode de construction du Handler

//空参的构造方法,这个方法调用了两个参数的构造方法
  public Handler() {
        this(null, false);
    }

//两个参数的构造方法
public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • Le Looper sera vérifié dans la méthode de construction du Handler. Si le Looper est vide, une exception de pointeur nul sera levée.
  • Handler fait également une chose dans la méthode de construction, pointant l'un de ses objets de file d'attente de messages globale (mQueue) vers la file d'attente de messages dans Looper, c'est-à-dire cette ligne de code dans la méthode de construction mQueue = mLooper.mQueue ;

La deuxième ligne de code initialise le Hanlder et remplace la méthode HandleMessage(). Il n'y a pas grand chose à dire.

Ensuite, la méthode Looper.loop() est appelée, ce qui sera expliqué plus tard.

Jetons d'abord un coup d'œil à la fonction principale de la dernière ligne du code handler.sendMessage(message) :

Examinons d'abord le flux d'exécution du code après cette ligne de code :

Après sendMessage(), le code exécute enfin la méthode enqueueMessage() de MessageQueue via plusieurs méthodes illustrées dans la figure. Jetons-y un coup d'œil :

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • MessageQueue est une structure de liste à sens unique, et la principale tâche de la méthode enqueueMessage() de MessageQueue est d'insérer le message envoyé par le gestionnaire dans la liste.
  • Lorsque la méthode handler.senMessage() est appelée, le résultat final consiste simplement à insérer le message dans la file d'attente des messages.

Le travail d’envoi du message est terminé, alors quand Looper a-t-il reçu le message ?

public static void loop() {
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);
            } 
        }
    }
  • C'est une boucle infinie
  • Le but de cette boucle est de récupérer le message depuis la MessageQueue
  • La méthode pour obtenir le message est la méthode MessageQueue.next()
  • Après avoir récupéré le message, appelez la méthode dispatchMessage() de l'objet message.target pour distribuer le message.
  • La condition de sortie de boucle est que la méthode MessageQueue.next() renvoie null.

En voyant cela, nous devrions naturellement nous demander quel objet est message.target.? Que fait dispatchMessage ?

public final class Message implements Parcelable {
 /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;
}
  • Une fois que le Looper a récupéré le message de MessageQueue, il appelle la méthode dispatchMessage() du gestionnaire.
    Ici, nous ne pouvons pas nous empêcher de demander vers quel gestionnaire cette cible pointe. Jetons un coup d'œil au message de mise en file d'attente précédent.
//Handler的方法
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • La première ligne de code consiste à attribuer l'attribut cible du message au gestionnaire lui-même qui envoie le message.
  • Une fois que le Looper a récupéré le message, il appelle la méthode dispatchMessage() du gestionnaire qui a envoyé le message et renvoie le message lui-même en tant que paramètre. À ce stade, la logique d’exécution du code est revenue au gestionnaire.

Regardez ensuite la méthode dispatchMessage() du gestionnaire

/**
    *handler的方法
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Voyez-vous ici une méthode que nous connaissons très bien ? —La méthode handleMessage() est également notre logique lors du traitement des messages.

Enfin, voici un diagramme schématique du fonctionnement du système :

6. Utilisation du gestionnaire (différence entre Android et Java)

6.1 Actualiser l'interface utilisateur

En utilisant Java :

new Thread( new Runnable() {     
    public void run() {     
         myView.invalidate();    
     }            
}).start();

Des fonctions peuvent être implémentées et l'interface utilisateur actualisée. Mais cela ne fonctionne pas car cela viole le modèle monothread : les opérations de l'interface utilisateur Android ne sont pas thread-safe et ces opérations doivent être effectuées dans le thread de l'interface utilisateur.

Thread+Handler :
le gestionnaire gère les mises à jour de l’interface utilisateur en fonction des messages reçus. Le thread Thread envoie un message Handler pour informer l’interface utilisateur de la mise à jour.

Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case TestHandler.GUIUPDATEIDENTIFIER:   
                         myBounceView.invalidate();  
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     }; 
class myThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                       
                    Message message = new Message();   
                    message.what = TestHandler.GUIUPDATEIDENTIFIER;   
                      
                    TestHandler.this.myHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     }   

6.2 Minuterie (fonctionnement différé)

Utiliser Java :
utilisez la classe TimerTask fournie avec Java. TimerTask consomme moins de ressources que Thread. Introduisez import java.util.Timer ; et import java.util.TimerTask ;

public class JavaTimer extends Activity {  
  
    Timer timer = new Timer();  
    TimerTask task = new TimerTask(){   
        public void run() {  
            setTitle("hear me?");  
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
       
         timer.schedule(task, 10000);   // 延迟delay毫秒后,执行一次task。

    }  
}

TimerTask + Gestionnaire :

public class TestTimer extends Activity {  
  
    Timer timer = new Timer();  
    Handler handler = new Handler(){   
        public void handleMessage(Message msg) {  
            switch (msg.what) {      
            case 1:      
                setTitle("hear me?");  
                break;      
            }      
            super.handleMessage(msg);  
        }  
          
    };  

    TimerTask task = new TimerTask(){    
        public void run() {  
            Message message = new Message();      
            message.what = 1;      
            handler.sendMessage(message);    
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
    
        timer.schedule(task, 10000);   // 延迟delay毫秒后,执行一次task。
    }  
}  

6.3 Mettre régulièrement à jour l'interface utilisateur

Exécutable + Handler.postDelayed(runnable,time):

   private Handler handler = new Handler();  
  
    private Runnable myRunnable= new Runnable() {    
        public void run() {  
            if (run) {  
                handler.postDelayed(this, 1000);  
                count++;  
            }  
            tvCounter.setText("Count: " + count);  

        }  
    }; 

puis appelle-le ailleurs

handler.post(myRunnable);
handler.post(myRunnable,time);

6.4 Le gestionnaire implémente une exécution retardée

Le gestionnaire retarde 2 secondes pour exécuter un exécutable

Handler handler=new Handler();
Runnable runnable=new Runnable() {
 @Override
	public void run() {
		// TODO Auto-generated method stub
		if(xsLayout.getVisibility()==View.VISIBLE){
			xsLayout.setVisibility(View.GONE);
		}
	}
};
handler.postDelayed(runnable, 2000);

Annulez cette tâche planifiée avant que l'exécutable ne soit exécuté

handler.removeCallbacks(runnable);

7. Résumé

7.1 Résumé

Derrière Handler se trouve l'assistance de Looper et MessageQueue. Les trois travaillent ensemble et ont une division claire du travail.

  • Looper : responsable des threads associés et de la distribution des messages. Il obtient le message de MessageQueue sous ce thread et le distribue au gestionnaire ;
  • MessageQueue : Il s'agit d'une file d'attente, responsable du stockage et de la gestion des messages, ainsi que de la gestion des messages envoyés par Handler ;
  • Gestionnaire : responsable de l'envoi et du traitement des messages, face aux développeurs, fournissant l'API et masquant les détails d'implémentation derrière celle-ci.

Les messages envoyés par le Handler sont stockés et gérés par MessageQueue, et le Loopler est chargé de rappeler les messages à handleMessage().

La conversion des threads est complétée par Looper, et le thread où se trouve handleMessage() est déterminé par le thread où se trouve l'appelant de Looper.loop().

7.2 Remarque

(1) Combien de gestionnaires un thread possède-t-il ?

  • Plusieurs, généralement plus d'un gestionnaire, seront créés au cours de notre processus de développement.

(2) Combien de Loopers un fil possède-t-il ? Comment garantir ?

  • Un seul boucleur
  • La construction de Looper est privée et ne peut être construite que via sa méthode Prepare(). Lorsque la méthode Prepare() de Looper est appelée, la méthode get() dans ThreadLocal sera appelée pour vérifier si Looper a été défini dans ThreadLocalMap ?
  • Si tel est le cas, une exception sera levée, indiquant que chaque thread ne peut avoir qu'un seul Looper. Sinon, un nouvel objet Looper sera défini dans ThreadLocalMap.
  • Cela garantit une correspondance biunivoque entre ThreadLocalMap et Looper, c'est-à-dire qu'un ThreadLocalMap ne correspondra qu'à un seul Looper. Le ThreadLocalMap ici est une variable globale dans Thread, et il n'y en aura qu'une, on peut donc garantir qu'il n'y a qu'un seul Looper dans un Thread.

(3) Quelle est la raison de la fuite de mémoire du gestionnaire ?

  • Les classes internes contiennent des références à des objets externes.
  • Principe du Handler : Puisque le Handler peut envoyer des messages retardés, afin de garantir que le message soit reçu par le même Handler après son exécution, le Message envoyé contiendra une référence au Handler. Cette référence est stockée dans le champ cible du Message. et appartient au gestionnaire. La méthode sendMessage() finira par appeler enqueueMessage(), et dans enqueueMessage(), cela sera attribué au champ cible du message.
  • Par conséquent, Message contient une référence à Handler et Handler contient une référence à Activity. Par conséquent, si l'activité est détruite avant le traitement du message, une fuite de mémoire se produira.
  • Comment y faire face? Les objets gestionnaires peuvent être modifiés à l’aide de static.

(4) Pourquoi le thread principal peut-il créer un nouveau gestionnaire ? Quelles préparations devez-vous effectuer si vous souhaitez utiliser un nouveau gestionnaire dans un thread enfant ?

  • Étant donné que main() dans ActivityThread a déjà effectué l'opération prepare() sur Looper, vous pouvez créer un nouveau gestionnaire directement dans le thread principal.
  • Dans la classe SystemServer d'Android-28 :
  • La méthode principale est l'entrée de l'ensemble de l'application Android. Looper.prepare() est appelée dans le thread enfant pour créer un objet Looper et stocker l'objet dans le ThreadLocal du thread actuel. Chaque thread aura un ThreadLocal, qui est Threads. fournir un mécanisme de copie locale de variable pour obtenir une isolation des autres threads.Cette variable ne fonctionne que pendant le cycle de vie de ce thread,ce qui peut réduire la complexité du transfert de variables publiques entre plusieurs méthodes dans le même thread. La méthode Looper.loop() consiste à retirer le message de la file d'attente des messages et à envoyer le message au gestionnaire spécifié, via la méthode msg.target.dispatchMassage().
    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }


    private void run() {
        ......
        Looper.prepareMainLooper();
        ......
    }
  • Si vous souhaitez créer un nouveau gestionnaire dans un thread enfant, vous devez appeler manuellement la méthode prepare() de Looper pour initialiser Looper, puis appeler la méthode loop() de Looper pour exécuter Looper.
      new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();			//初始化Looper
                new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
                Looper.loop();
            }
        })

(5) Looper maintenu dans le thread enfant, quelle est la solution lorsqu'il n'y a pas de message dans la file d'attente des messages ?

  • S'il n'est pas géré, le thread sera bloqué. La solution est d'appeler la fonction quitSafely();
  • quitSafely() appellera la méthode quit() de MessageQueue, effacera tous les messages et appellera la méthode nativeWake() pour réveiller nativePollOnce() précédemment bloquée, afin que la boucle for de la méthode next() continue de s'exécuter, puis on constate que le message est après null, la boucle se terminera et le Looper se terminera. Cela libère de la mémoire et des threads.

(6) Comment garantir la sécurité des threads en interne ?

  • Il peut y avoir plusieurs gestionnaires ajoutant des données à MessageQueue (chaque gestionnaire peut être dans un thread différent lors de l'envoi d'un message).
    La méthode d'ajout d'un message, enqueueMessage(), est modifiée par synchroniser, et la méthode de récupération d'un message, ensuite (), est également modifié par synchroniser.
  • L’heure du message de retard du gestionnaire est-elle exacte ?
    En raison de l’opération de verrouillage ci-dessus, l’heure ne peut pas être garantie comme étant complètement exacte.

(7) Comment dois-je le créer lorsque j'utilise Message ?

  • Créé à l'aide de la méthode get() de Message, sa création directe provoquera facilement une instabilité de la mémoire.
  • La gigue de la mémoire est causée par de nouveaux objets fréquents et une collecte fréquente des déchets par gc, et comme ils peuvent être conservés ailleurs et ne peuvent pas être recyclés à temps, l'utilisation de la mémoire deviendra de plus en plus élevée.
  • Utiliser get() pour réutiliser la mémoire peut éviter la gigue de la mémoire. Il maintient un pool de messages en interne, qui est une structure de liste chaînée. Lorsque get() est appelé, le message en tête de la table sera réutilisé puis pointé vers le suivant. S'il n'y a pas de message réutilisable dans l'en-tête, un nouvel objet sera créé. La longueur maximale de ce pool d'objets est de 50.

(8) Qu'arrivera-t-il à la file d'attente des messages après avoir utilisé postDelay de Handler ?

  • Si la file d'attente des messages est vide à ce moment-là, il ne sera pas exécuté. Le temps d'attente du message sera calculé et l'exécution se poursuivra lorsque le temps d'attente sera écoulé.

(9) Pourquoi la boucle infinie de Looper ne provoque-t-elle pas le blocage de l'application ?

  • L'ANR est bloqué, il y a deux raisons :
  • 1. Il n'y a aucune réponse aux événements d'entrée (tels que les pressions sur des touches, les touches, etc.) dans les 5 secondes.
  • 2. BroadcastReceiver n’a pas terminé l’exécution dans les 10 secondes.
  • En fait, toutes nos activités et services s'exécutent dans la fonction loop() et existent sous forme de messages, donc lorsqu'aucun message n'est généré, le looper sera bloqué (bloqué) et le thread principal se mettra en veille. un événement d'entrée Ou le thread principal sera réveillé après que le Looper ait ajouté un message pour répondre à l'événement, cela ne provoquera donc pas d'ANR.
  • En termes simples, le blocage du boucleur indique qu'il n'y a pas d'entrée d'événement et l'ANR est provoqué par la non-réponse de l'événement, donc la boucle infinie du boucleur ne provoquera pas le gel de l'application.

6.3 Suivant

Introduction

Dans le système Android, après avoir effectué des opérations fastidieuses, nous devons démarrer un autre sous-thread pour l'exécution. Une fois le thread exécuté, le thread sera automatiquement détruit. Imaginez si nous effectuons souvent des opérations fastidieuses dans le projet, si nous démarrons souvent des threads puis les détruisons, cela consommera sans aucun doute beaucoup de performances.

HandlerThread est encapsulé par Google et peut être utilisé pour effectuer plusieurs opérations fastidieuses sans démarrer le thread plusieurs fois. Il est implémenté à l'aide de Handler et Looper.

HanderThread est en fait un fil de discussion

comment utiliser?

//创建实例对象,该参数表示线程的名字
HandlerThread handlerThread = new HandlerThread("myHandlerThread");

//启动我们创建的HandlerThread线程
handlerThread.start();

//怎样将Handler与线程对象绑定在一起
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
	@Override
	public void handleMessage(Message msg) {
		//发生myHandlerThread线程中
		checkForUpdate();
		if(isUpdate){
			mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
		}
	}
};

Regardez un exemple

public class MainActivity extends AppCompatActivity {
	private static final int MSG_UPDATE_INFO = 0x100;
	Handler mMainHandler = new Handler();
	private TextView mTv;
	private Handler mThreadHandler;
	private HandlerThread mHandlerThread;
	private boolean isUpdate = true;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mTv = (TextView) findViewById(R.id.tv);
		initHandlerThread();
	}
	private void initHandlerThread() {
		mHandlerThread = new HandlerThread("xujun");
		mHandlerThread.start();
		mThreadHandler = new Handler(mHandlerThread.getLooper()){
			@Override
			public void handleMessage(Message msg) {
				checkForUpdate();
				if (isUpdate) {
					mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
				}
			}
		};
	}
	
/**
* 模拟从服务器解析数据
*/
	private void checkForUpdate() {
		try {
			//模拟耗时
			Thread.sleep(1200);
			mMainHandler.post(new Runnable() {
				@Override
				public void run() {
					String result = "实时更新中,当前股票行情:<fontcolor='red'>%d</font>";
					result = String.format(result, (int) (Math.random() * 5000 + 1000));
					mTv.setText(Html.fromHtml(result));
				}	
			});
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected void onResume() {
		isUpdate = true;
		super.onResume();
		mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
	}
	@Override
	protected void onPause() {
		super.onPause();
		isUpdate = false;
		mThreadHandler.removeMessages(MSG_UPDATE_INFO);
	}
@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandlerThread.quit();
		mMainHandler.removeCallbacksAndMessages(null);
	}
}

Exécutez le code de test ci-dessus et vous verrez les rendus suivants (l'exemple n'est pas approprié, le scénario d'utilisation principal consiste à effectuer des opérations fastidieuses dans handleMessage)

Regardez le code source

Sur la base du sdk23, vous pouvez voir qu'il n'y a que plus d'une centaine de lignes de code :

public class HandlerThread extends Thread {
	int mPriority;
	int mTid = -1;
	Looper mLooper;
	public HandlerThread(String name) {
		super(name);
		mPriority = Process.THREAD_PRIORITY_DEFAULT;
	}
	public HandlerThread(String name, int priority) {
		super(name);
		mPriority = priority;
	}
/**
* Call back method that can be explicitly overridden if nee
ded to execute some
* setup before Looper loops.
*/
	protected void onLooperPrepared() {
	}
	
	@Override
	public void run() {
		mTid = Process.myTid();
		Looper.prepare();
		//持有锁机制来获得当前线程的Looper对象
		synchronized (this) {
			mLooper = Looper.myLooper();
			//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
			notifyAll();
		}
		//设置线程的优先级别
		Process.setThreadPriority(mPriority);
		//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
		onLooperPrepared();
		Looper.loop();
		mTid = -1;
	}
	
	public Looper getLooper() {
		if (!isAlive()) {
		return null;
		}
		// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
		synchronized (this) {
			while (isAlive() && mLooper == null) {
				try {
					wait();
				} catch (InterruptedException e) {
				}
			}
		}
		return mLooper;
	}
	
	public boolean quit() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quit();
			return true;
		}
		return false;
	}
	
	public boolean quitSafely() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quitSafely();
			return true;
		}
		return false;
	}
/**
* Returns the identifier of this thread. See Process.myTid(
).
*/
	public int getThreadId() {
		return mTid;
	}
}

(1) Premier regard sur le constructeur

	public HandlerThread(String name) {
		super(name);
		mPriority = Process.THREAD_PRIORITY_DEFAULT;
	}
	public HandlerThread(String name, int priority) {
		super(name);
		mPriority = priority;
	}

Un paramètre et deux paramètres, name représente le nom du thread actuel, priorité est le niveau de priorité du thread

(2) Jetez un œil à la méthode run().
Dans la méthode run, nous pouvons voir que nous allons initialiser un Looper et définir le niveau de priorité du thread.

	public void run() {
		mTid = Process.myTid();
		Looper.prepare();
		//持有锁机制来获得当前线程的Looper对象
		synchronized (this) {
			mLooper = Looper.myLooper();
			//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
			notifyAll();
		}
		//设置线程的优先级别
		Process.setThreadPriority(mPriority);
		//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
		onLooperPrepared();
		Looper.loop();
		mTid = -1;
	}

Plus tôt, nous avons mentionné que lors de l'utilisation de HandlerThread, nous devons appeler la méthode start(), puis nous pouvons lier notre HandlerThread et notre gestionnaire ensemble.

  • La raison en est que nous commençons seulement à initialiser notre looper dans la méthode run(), et lorsque nous appelons la méthode start() de HandlerThread, le thread sera transmis à la machine virtuelle pour la planification, et la machine virtuelle appellera automatiquement la méthode d'exécution.

Pourquoi utiliser le mécanisme de verrouillage et notifyAll() :

  • Nous pouvons connaître la raison grâce à la méthode getLooper()
	public Looper getLooper() {
		if (!isAlive()) {
			return null;
		}
	// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
	synchronized (this) {
		while (isAlive() && mLooper == null) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}
	return mLooper;
}

Résumé : Il y a un problème de synchronisation lors de l'obtention de l'objet mLooper. La valeur de mLooper ne peut être obtenue que lorsque le thread est créé avec succès et que l'objet Looper est également créé avec succès. Ici, la méthode wait wait et la méthode notifyAll dans la méthode run résolvent conjointement le problème de synchronisation.

(3) Jetons ensuite un coup d'œil à la méthode quit et à la méthode quitSafe

	public boolean quit() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quit();
			return true;
		}
		return false;
	}
	
	public boolean quitSafely() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quitSafely();
			return true;
		}
		return false;
	}

Il est facile de suivre ces deux méthodes et de savoir que seules deux méthodes finiront par appeler la méthode quit (boolean safe) de MessageQueue.

void quit(boolean safe) {
	if (!mQuitAllowed) {
		throw new IllegalStateException("Main thread not allowedto quit.");
	}
	synchronized (this) {
		if (mQuitting) {
			return;
		}
		mQuitting = true;
		//安全退出调用这个方法
		if (safe) {
			removeAllFutureMessagesLocked();
		} else {//不安全退出调用这个方法
			removeAllMessagesLocked();
		}
		// We can assume mPtr != 0 because mQuitting was previously false.
		nativeWake(mPtr);
	}
}

Si ce n'est pas sûr, removeAllMessagesLocked(); sera appelé. Voyons comment cette méthode est gérée. Elle parcourt en fait la liste chaînée des messages, supprime les rappels de tous les messages et les réinitialise à null.

private void removeAllMessagesLocked() {
	Message p = mMessages;
	while (p != null) {
		Message n = p.next;
		p.recycleUnchecked();
		p = n;
	}
	mMessages = null;
}

Appelez en toute sécurité removeAllFutureMessagesLocked(); Cette méthode déterminera si notre file d'attente de messages actuelle traite les messages en fonction de la propriété Message.when. Si aucun message n'est en cours de traitement, tous les rappels seront supprimés directement. Si c'est le cas, attendez que le message soit Traitement. Quittez la boucle après le traitement. Par conséquent, quitSafe() est sûr, mais la méthode quit() n'est pas sûre, car la méthode quit supprime directement tous les rappels, que le message soit ou non en cours de traitement.

private void removeAllFutureMessagesLocked() {
	final long now = SystemClock.uptimeMillis();
	Message p = mMessages;
	if (p != null) {
		//判断当前队列中的消息是否正在处理这个消息,没有的话,直接移除所有回调
		if (p.when > now) {
			removeAllMessagesLocked();
		} else {
			//正在处理的话,等待该消息处理处理完毕再退出该循环
			Message n;
			for (;;) {
				n = p.next;
				if (n == null) {
					return;
				}
				if (n.when > now) {
					break;
				}
				p = n;
			}
			p.next = null;
			do {
				p = n;
				n = p.next;
				p.recycleUnchecked();
			} while (n != null);
		}
	}
}

Je suppose que tu aimes

Origine blog.csdn.net/qq_32907491/article/details/132645806
conseillé
Classement