Details of Android message handling mechanisms Looper and Handler

  • 2020-06-03 08:28:07
  • OfStack


Message : Message, which contains the message ID , message processing objects and data processed by MessageQueue system 1 Line up, all right Handler To deal with. 
Handler : Handler, responsible Message Sending and processing. use Handler , you need to implement handleMessage(Message msg) The method comes to the specific Message To process, such as update UI And so on. 
MessageQueue : Message queue used to store messages Handler Send the message over and follow it FIFO Rule execution. Of course, storage Message Not the actual preservation, but will Message Concatenated in a linked list, waiting Looper The extraction. 
Looper : Message pump, constantly from MessageQueue Extract the Message The execution. As a result, 1 a MessageQueue Need to be 1 a Looper . 
Thread : Thread, responsible for scheduling the entire message loop, that is, the execution place of the message loop. 

The message queue and message loop of Android system are specific to the specific thread. One thread can exist (or not exist), one message queue and one message elimination loop (Looper). Messages of specific thread can only be distributed to this thread, and can't communicate across threads or processes. But the worker thread created has no message loop or message queue by default. If you want the thread to have message queue and message loop, you need to call Looper.prepare () in the thread to create the message queue and then call Looper.loop () to enter the message loop. As shown in the following example:


 LooperThread Thread {
    Handler mHandler;

    run() {
     Looper.prepare();

     mHandler = Handler() {
        handleMessage(Message msg) {
         
       }
     };

     Looper.loop();
   }
 }

/ / Looper class analysis
// I didn't find a proper way to analyze the code, so I had to do it this way. Each important line is annotated at the top
// Functional code is preceded by a paragraph of analysis


 public class Looper {
  //static Variable to determine whether to print debugging information. 
   private static final boolean DEBUG = false;
   private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
 
   // sThreadLocal.get() will return null unless you've called prepare().
 // Encapsulation of thread-local storage, TLS . thread local storage, What does that mean? Because the storage is either on the stack, such as internal variables defined within a function. Or on the heap, for example new or malloc Things that come out 
 // But the current system for example Linux and windows Both provide thread-local storage, that is, storage that is thread specific, 1 Within a thread 1 Internal storage, so That I can store things that are related to threads 
 // The threaded TLS Instead of putting it on the heap, you can synchronize it. 
   private static final ThreadLocal sThreadLocal = new ThreadLocal();
 // Message queues, MessageQueue You can tell by the name queue..
   final MessageQueue mQueue;
   volatile boolean mRun;
 // And this looper The associated thread, initialized to null
   Thread mThread;
   private Printer mLogging = null;
 //static Variable, stands for 1 a UI Process It could be service Well, the default here is UI ) 
   private static Looper mMainLooper = null;
   
   /** Initialize the current thread as a looper.
    * This gives you a chance to create handlers that then reference
    * this looper, before actually starting the loop. Be sure to call
    * {@link #loop()} after calling this method, and end it by calling
    * {@link #quit()}.
    */
 // to TLS I'm going to set that Looper Object will report an error if the thread is already set to looper 
 // This shows, 1 The number of threads can only be set 1 a looper
   public static final void prepare() {
     if (sThreadLocal.get() != null) {
       throw new RuntimeException("Only one Looper may be created per thread");
     }
     sThreadLocal.set(new Looper());
   }
   
   /** Initialize the current thread as a looper, marking it as an application's main 
   * looper. The main looper for your application is created by the Android environment,
   * so you should never need to call this function yourself.
   * {@link #prepare()}
   */
 // by framework Set up the UI The program's main message loop. Note that the main message loop will not exit voluntarily 
 //  
   public static final void prepareMainLooper() {
     prepare();
     setMainLooper(myLooper());
 // Determine if the main message loop can exit ....
 // through quit Function to looper Issue of withdrawal Application 
     if (Process.supportsProcesses()) {
       myLooper().mQueue.mQuitAllowed = false;
     }
   }
 
   private synchronized static void setMainLooper(Looper looper) {
     mMainLooper = looper;
   }
   
   /** Returns the application's main looper, which lives in the main thread of the application.
   */
   public synchronized static final Looper getMainLooper() {
     return mMainLooper;
   }
 
   /**
   * Run the message queue in this thread. Be sure to call
   * {@link #quit()} to end the loop.
   */
 // Message loop, where the whole program is while . 
 // This is a static The function! 
   public static final void loop() {
     Looper me = myLooper();// Fetch the corresponding from the thread looper object 
     MessageQueue queue = me.mQueue;// Gets the message queue object ...
     while (true) {
       Message msg = queue.next(); // might block Gets from the message queue 1 A message to be processed ..
       //if (!me.mRun) {// Do I need to quit? mRun Is a volatile Variables, synchronized across threads, should have somewhere to set it. 
       //  break;
       //}
       if (msg != null) {
         if (msg.target == null) {
           // No target is a magic identifier for the quit message.
           return;
         }
         if (me.mLogging!= null) me.mLogging.println(
             ">>>>> Dispatching to " + msg.target + " "
             + msg.callback + ": " + msg.what
             );
         msg.target.dispatchMessage(msg);
         if (me.mLogging!= null) me.mLogging.println(
             "<<<<< Finished to  " + msg.target + " "
             + msg.callback);
         msg.recycle();
       }
     }
   }
 
   /**
   * Return the Looper object associated with the current thread. Returns
   * null if the calling thread is not associated with a Looper.
  */
// Returns the thread - related looper
 public static final Looper myLooper() {
   return (Looper)sThreadLocal.get();
 }

 /**
  * Control logging of messages as they are processed by this Looper. If
  * enabled, a log message will be written to <var>printer</var> 
  * at the beginning and ending of each message dispatch, identifying the
  * target Handler and message contents.
  * 
  * @param printer A Printer object that will receive log messages, or
  * null to disable message logging.
  */
// Set the debug output object, looper The loop will print the information, which is best used for debugging. 
 public void setMessageLogging(Printer printer) {
   mLogging = printer;
 }
 
 /**
  * Return the {@link MessageQueue} object associated with the current
  * thread. This must be called from a thread running a Looper, or a
  * NullPointerException will be thrown.
  */
 public static final MessageQueue myQueue() {
   return myLooper().mQueue;
 }
// create 1 A new one looper Object, 
// The internal distribution 1 Message queue, Settings mRun for true
 private Looper() {
   mQueue = new MessageQueue();
   mRun = true;
   mThread = Thread.currentThread();
 }

 public void quit() {
   Message msg = Message.obtain();
   // NOTE: By enqueueing directly into the message queue, the
   // message is left with a null target. This is how we know it is
   // a quit message.
   mQueue.enqueueMessage(msg, 0);
 }

 /**
  * Return the Thread associated with this Looper.
  */
 public Thread getThread() {
   return mThread;
 }
 // The rest is simple, printing, exception definition and so on. 
 public void dump(Printer pw, String prefix) {
   pw.println(prefix + this);
   pw.println(prefix + "mRun=" + mRun);
   pw.println(prefix + "mThread=" + mThread);
   pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null"));
   if (mQueue != null) {
     synchronized (mQueue) {
       Message msg = mQueue.mMessages;
       int n = 0;
       while (msg != null) {
         pw.println(prefix + " Message " + n + ": " + msg);
         n++;
         msg = msg.next;
       }
       pw.println(prefix + "(Total messages: " + n + ")");
     }
   }
 }

 public String toString() {
   return "Looper{"
     + Integer.toHexString(System.identityHashCode(this))
     + "}";
 }

 static class HandlerException extends Exception {

   HandlerException(Message message, Throwable cause) {
     super(createMessage(cause), cause);
   }

   static String createMessage(Throwable cause) {
     String causeMsg = cause.getMessage();
     if (causeMsg == null) {
       causeMsg = cause.toString();
     }
     return causeMsg;
   }
 }
}

So how do you send a message to this message queue? Call looper's static function myQueue to get the message queue so you can insert your own messages into it. But this is a bit of a hassle, and that's where the handler class comes into play. Take a look at the handler code.


 class Handler{
 ..........
 //handler Default constructor 
 public Handler() {
 // this if It is not clear for the moment what it is used for java The depth of the content should be 
     if (FIND_POTENTIAL_LEAKS) {
       final Class<? extends Handler> klass = getClass();
       if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
           (klass.getModifiers() & Modifier.STATIC) == 0) {
         Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
           klass.getCanonicalName());
       }
     }
 // Gets the thread's looper object 
 // If this thread is not already set looper , this time the toss is abnormal 
     mLooper = Looper.myLooper();
     if (mLooper == null) {
       throw new RuntimeException(
         "Can't create handler inside thread that has not called Looper.prepare()");
     }
 // Shameless, just take it looper the queue And his own queue Get into 1 A the 
 // In that case, I pass handler Encapsulation mechanism plus message, equivalent to directly added to looper Went to the message queue 
     mQueue = mLooper.mQueue;
     mCallback = null;
   }
 // There are several constructors, 1 Is the belt callback The, 1 Is the belt looper the 
 // Set externally looper
   public Handler(Looper looper) {
     mLooper = looper;
     mQueue = looper.mQueue;
     mCallback = null;
   }
 //  with callback The, 1 a handler You can set the 1 a callback . If you have callback Words, 
 // Usually I send it through this handler The messages are sent callback To deal with, equal to 1 Individual concentration processing 
 // Forwarder will see dispatchMessage Analyze it later 
 public Handler(Looper looper, Callback callback) {
     mLooper = looper;
     mQueue = looper.mQueue;
     mCallback = callback;
   }
 //
 // through handler Send a message 
 // Internal 1 a sendMessageDelayed
 public final boolean sendMessage(Message msg)
   {
     return sendMessageDelayed(msg, 0);
   }
 //FT ", again encapsulated 1 Layer, this time calling sendMessageAtTime the 
 // Because the latency time is based on the current call time, you need to get the absolute time passed to sendMessageAtTime
 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)
   {
     boolean sent = false;
     MessageQueue queue = mQueue;
     if (queue != null) {
 // The news of the target Set to yourself, and then join the message queue 
 // For data structures like queues, the operation is relatively simple 
       msg.target = this;
       sent = queue.enqueueMessage(msg, uptimeMillis);
     }
     else {
       RuntimeException e = new RuntimeException(
         this + " sendMessageAtTime() called with no mQueue");
       Log.w("Looper", e.getMessage(), e);
     }
     return sent;
   }
 // Do you remember looper Is the message loop processed in 
 // From the message queue 1 After a message, it will be called target the dispatchMesage function 
 //message the target Set to handler , so 
 // And then we'll end up with theta handler the msg Handle up 
 // There's a process problem here 
 public void dispatchMessage(Message msg) {
 // if msg It's set up callback , and I'll just give it to this one callback Deal with the 
     if (msg.callback != null) {
       handleCallback(msg);
     } else {
 // If the handler the callback If there is, give it to this callback Deal with the --- Equivalent to centralized processing 
      if (mCallback != null) {
         if (mCallback.handleMessage(msg)) {
           return;
         }
      }
 // Otherwise, leave it to the derivation , By default, the base class handles nothing 
       handleMessage(msg);
     }
   }
 ..........
 }

generate


    Message msg = mHandler.obtainMessage();
    msg.what = what;
    msg.sendToTarget();

send


    MessageQueue queue = mQueue;
    if (queue != null) {
      msg.target = this;
      sent = queue.enqueueMessage(msg, uptimeMillis);
    }

In the sendMessageAtTime(Message msg, long uptimeMillis) method of ES32en.java, we see that it finds the MessageQueue it refers to, sets target of Message itself (so that Message can find the correct Handler in the process of messages), and then puts this Message on the message queue.

extracting


    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) {
      Message msg = queue.next(); // might block
      if (msg != null) {
        if (msg.target == null) {
          // No target is a magic identifier for the quit message.
          return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycle();
      }
    }

In the loop() function of ES50en.java, we can see that there is an infinite loop, constantly obtaining the next Message from MessageQueue (next method), and then passing the target information carried by Message to the correct Handler (dispatchMessage method).

To deal with


    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }

In the dispatchMessage(Message msg) method of ES65en.java, one of the branches is to call the handleMessage method to handle this Message, which is why we need to implement handleMessage(Message msg) when using Handler in the responsibility section.

As for the other branch in the dispatchMessage method, I will describe it later.

So far, we see that one Message is sent by Handler, the team of MessageQueue, the draw of Looper, and once again back to the embrace of Handler. And this 1 turn around also helps us make the synchronous operation asynchronous operation.

3) For the rest of the presentation, we will discuss the thread where Handler is under 1 and how to update UI.

In the main thread (UI thread), if the Looper object is not passed in when Handler is created, the Looper object of the main thread (UI thread) is directly used (the system has already created it for us). In other threads, if an Looper object is not passed in when Handler is created, the Handler will not receive processed messages. In this case, the general practice is:


        class LooperThread extends Thread {
                public Handler mHandler;
                public void run() {
                        Looper.prepare();
                        mHandler = new Handler() {
                                public void handleMessage(Message msg) {
                                       // process incoming messages here
                                }
                        };
                        Looper.loop();
                }
        }

Before creating Handler, prepare 1 Looper (ES105en.prepare) for the thread, then let the Looper run (Looper.loop), extract Message, so that Handler can work properly.

Therefore, Handler processing messages are always run in the same thread that created Handler. In our message processing, there is no lack of update UI operation, the incorrect thread directly update UI will throw an exception. Therefore, you need to keep an eye on which thread Handler was created in.

How can I update UI without causing an exception? SDK tells us that there are four ways to access UI threads from other threads:

・ Activity. runOnUiThread (Runnable)
・ View. post (Runnable)
・ View. postDelayed (Runnable long)
・ Handler
Among them, the emphasis under 1 is View.post(Runnable) method. In the post(Runnable action) method, View gets Handler for the current thread (that is, UI thread) and then adds action object post to Handler. In Handler, it wraps the action object passed to it as an Message (Message's callback is action) and then drops it into the message loop of the UI thread. When Handler processes the Message again, one branch (the unexplained one) is set for it, calling the run method of runnable directly. At this point, it has been routed to the UI thread, so we have no problem updating UI.

4) Some summary

· The processing of Handler runs in the thread that created Handler
· One Looper corresponds to one MessageQueue
· One thread corresponds to one Looper
· One Looper can correspond to multiple Handler
· When the current thread is uncertain, try to call the post method when updating UI


Related articles: