The relevant content of the article has been authorized to “Guo Lin” public account published

preface

Nice to meet you ~ welcome to read my article.

This article, the third in a series, introduces the Handler’s internal key classes: Message, MessageQueue, and Looper. This article begins with an introduction to the underlying principles of Handler.

The Handler series is divided into six parts:

  1. Part 1: Start the series with Handler from 0;
  2. The second part introduces the internal schema of Handler, and elaborates on the key class ThreadLocal.
  3. The third part: parsing Handler internal key classes: Message, MessageQueue, Looper;
  4. Part 4: Handler internal key class: Handler, at the same time introduce HandlerThread;
  5. The fifth part: summarize Handler, think Handler from the perspective of source code design;
  6. Part 6: Handler FAQ;

This is the third article in a series. Readers can go to the author’s home page and select the sections they are interested in.

So, let’s get started.

The body of the

Message

An overview of the

Message is the class responsible for carrying messages, focusing on its internal attributes:

// User-defined, mainly used to identify the type of Message
public int what;
// Used to store some integer data
public int arg1;
public int arg2;
// Can put a serializable object public Object obj; / / data Bundle Bundle data; // Message processing time. Time relative to 1970.1.1 // Not visible to the user public long when; // The Handler that handles this Message // Not visible to the user Handler target; // When we use the Handler post method, we wrap the runnable object as a Message // Not visible to the user Runnable callback; // MessageQueue is a linked list, and next represents the next list // Not visible to the user Message next; Copy the code

Recycling messages

When we obtain messages, the official recommendation is to obtain them through the message.obtain () method and recycle() when we’re done using them. Instead of simply new a new object:

public static Message obtain(a) {
    synchronized (sPoolSync) {
        if(sPool ! =null) {
            Message m = sPool;
            sPool = m.next;
 m.next = null;  m.flags = 0;  sPoolSize--;  return m;  }  }  return new Message(); } Copy the code

Message maintains a static list with a sPool header. Message has a next property. Message is itself a list structure. SPoolSync is an object object designed only to address concurrent access security. When we call obtain to get a new Message, we first check to see if there are any free messages in the list and return a new one if there are none.

If the Message is being used, it will throw an exception. Otherwise, it will call recycleUnchecked for recycling, empty the contents of the Message, and judge whether the linked list has reached the maximum value (50). And then insert into the linked list. Relevant source readers can view, limited to space here will not show.

The Message summary

Message is used to carry messages, and it has a number of internal properties that are used to assign values to the user. Meanwhile, Message itself is also a linked list structure, which is used to form linked lists both in MessageQueue and in the recycling mechanism inside Message. At the same time, the official recommendation is not to initialize Message directly, but to obtain a Message recycle using the message.obtain () method. In general, we don’t need to call “Recycle” for recycling. In Looper, messages are automatically recycled, as we’ll see later.

MessageQueue

An overview of the

Each thread has one and only one MessageQueue, which is a queue for carrying messages. Internally, linked lists are used as data structures, so messages to be processed are queued here. ThreadLocalMap is a “modified HashMap” and MessageQueue is a “modified LinkQueue”. He also has two key methods: join the team (enqueueMessage) and get out (next). This is where MessageQueue focuses.

Message also touches on a key concept: thread sleep. When there is no message in MessageQueue or both messages are waiting, the thread is hibernated to release CPU resources and improve CPU utilization efficiency. After hibernation, you need to wake up the thread if you want to continue executing code. When the method is temporarily unable to return directly and needs to wait, the thread can be blocked, that is, hibernated, to be woken up to continue executing the logic. This will also be covered in more detail later.

The key way to

  • The team – the next ()

    The next method is mainly about sending out messages.

Message next(a) {
    // If looper has already exited, null is returned
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
 } . // Block time  int nextPollTimeoutMillis = 0;  for (;;) {  if(nextPollTimeoutMillis ! =0) {  Binder.flushPendingCommands();  }  // Block the corresponding time  nativePollOnce(ptr, nextPollTimeoutMillis);  // Lock MessageQueue to ensure thread safety  synchronized (this) {  final long now = SystemClock.uptimeMillis();  Message prevMsg = null;  Message msg = mMessages; . if(msg ! =null) {  if (now < msg.when) {  // The next message hasn't started yet  nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);  } else {  // Get the message and now execute, mark MessageQueue as non-blocking  mBlocked = false;  // List operations  if(prevMsg ! =null) {  prevMsg.next = msg.next;  } else {  mMessages = msg.next;  }  msg.next = null;  msg.markInUse();  return msg;  }  } else {  // There is no message and the state is blocked  nextPollTimeoutMillis = -1;  } . } } Copy the code

The code is long and involves synchronization barriers and idleHandlers, which I’ll cover later, but the main exit logic is here first. I’ve commented it all out in the code, but I’ll go over it again. The next method fetches a Message from a MessageQueue, and if there is no Message in the queue, blocks the method waiting for a new Message to wake it up. The main steps are as follows:

  1. If Looper has already exited, null is returned
  2. An infinite loop is entered until Message is retrieved or exit
  3. In the loop, determine whether to block at first, lock MessageQueue after the block, and obtain Message
  4. If there is no message in MessageQueue, the thread is directly blocked indefinitely waiting to wake up;
  5. If there is a message in the MessageQueue, it determines whether it needs to wait; otherwise, it returns the corresponding message directly.

You can see that the logic is to determine whether the current time Message needs to wait. Where nextPollTimeoutMillis indicates the blocking time, -1 indicates the infinite time, and the blocking can only be broken by wake up.

  • Team – enqueueMessage ()
MessageQueue.class

boolean enqueueMessage(Message msg.long when) {
    // Hanlder cannot be null
    if (msg.target == null) {
 throw new IllegalArgumentException("Message must have a target.");  }  if (msg.isInUse()) {  throw new IllegalStateException(msg + " This message is already in use.");  }   // Lock MessageQueue  synchronized (this) {  // Determine if the target thread is dead  if (mQuitting) {  IllegalStateException e = new IllegalStateException(  msg.target + " sending message to a Handler on a dead thread");  Log.w(TAG, e.getMessage(), e);  msg.recycle();  return false;  }  // Mark when Message is being executed and when it needs to be executed, where when is 1970.1.1 away  msg.markInUse();  msg.when = when;  // p is the header of MessageQueue  Message p = mMessages;  boolean needWake;  // Determine whether MessageQueue needs to be woken up  // If there is a new queue head and MessageQueue is blocked, the queue needs to be woken up  if (p == null || when == 0 || when < p.when) {  msg.next = p;  mMessages = msg;  needWake = mBlocked;  } else { . // Find the insertion position according to the time  Message prev;  for (;;) {  prev = p;  p = p.next;  if (p == null || when < p.when) {  break;  } . }  msg.next = p;  prev.next = msg;  }   // Wake up the queue if necessary  if (needWake) {  nativeWake(mPtr);  }  }  return true; } Copy the code

This part of the code seems to be a lot, but the logic is not complicated, mainly linked list operation and determine whether to wake up MessageQueue, I have added some comments in the code, the following summary:

  1. First check that message’s target handler cannot be empty and not in use

  2. Lock MessageQueue

  3. Check if the target thread is dead, or return false

  4. Initialize Message execution time and the tag is executing

  5. Then, based on the execution time of Message, find the insertion position in the linked list for insertion

  6. At the same time, determine whether MessageQueue needs to be awakened. There are two cases where you need to wake up a messageQueue when the newly inserted Message is in the head of the linked list, if the messageQueue is empty, or if you are waiting for the delay of the next task.

MessageQueue summary

Message has two key points: blocking sleep and queue operations. Basically, they all revolve around two things. The source code also involves synchronization barriers and IdleHandler, which I separate into the last part of the related issues. Usually used less, but also more important content.

Looper

An overview of the

Looper is a very important core of the Handler mechanism. Looper acts as the engine of the threaded messaging mechanism, driving the entire messaging mechanism. Looper is responsible for fetching the message from the queue and handing it to the corresponding Handler for processing. If there is no message in the queue, MessageQueue’s Next method blocks the thread, waiting for a new message to arrive. Each thread has one and only one “engine”, known as a Looper, without which the messaging mechanism will not work, while multiple LoOpers will violate the concept of single-line operations and result in concurrent operations.

There is only one Looper per thread, and messages distributed by different Loopers run in different threads. The Looper internally maintains a MessageQueue, which is initialized along with the Looper.

Looper uses ThreadLocal to ensure that each thread has one and only one identical copy.

The key way to

  • Prepare: Initializes Looper
Looper.class

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare(a) {
 prepare(true); }  // This method is finally called private static void prepare(boolean quitAllowed) {  if(sThreadLocal.get() ! =null) {  throw new RuntimeException("Only one Looper may be created per thread");  }  sThreadLocal.set(new Looper(quitAllowed)); } Copy the code

Before each thread can use Handler, the looper.prepare () method must be called to initialize the current thread’s Looper. Parameter quitAllowed indicates whether the Looper can exit. The main thread Looper cannot exit, otherwise the program terminates. We don’t initialize Looper when we use Handler on the main thread. Why? Because Activiy initializes the main thread Looper when it starts, which we’ll talk about later. So on the main thread we can call looper.mylooper () directly to get the current thread’s Looper.

Set (new Looper(quitAllowed)); , you can see that ThreadLocal is used to create a copy of the Looper object for the current thread. If the current thread already has a Looper, an exception is thrown. SThreadLocal is a static variable of the Looper class. It is used to initialize the Looper of the current thread by calling prepare once per thread.

Now look at the Looper constructor:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code

The logic is simple: initialize a MessageQueue and assign the Thread object of the current Thread to the mThread.

  • MyLooper () : Gets the Looper object of the current thread

    Gets the Looper object for the current thread. This method calls ThreadLocal’s get method directly:

public static @Nullable Looper myLooper(a) {
    return sThreadLocal.get();
}
Copy the code
  • Loop () : Loop to get the message

    After Looper is initialized, it will not start by itself. We need to start Looper by calling Looper’s loop() method:

public static void loop(a) {
    // Get the current thread's Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 }  final MessageQueue queue = me.mQueue; . for (;;) {  // Get the message in the message queue  Message msg = queue.next(); // might block  if (msg == null) {  // Return null indicating that MessageQueue exits  return;  } . try {  // Call the Handler corresponding to Message to process the Message  msg.target.dispatchMessage(msg);  if(observer ! =null) {  observer.messageDispatched(token, msg);  }  dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;  } . / / retrieve the Message  msg.recycleUnchecked();  } } Copy the code

The loop() method is at the heart of the Looper engine. The Looper object of the current thread is first fetched, and an exception is thrown if there is none. It then enters an endless loop: call MessageQueue’s Next method to get the message, then call the dispatchMessage method of Message’s target handler to process the message.

As we learned earlier about MessageQueue, the next method can block when the MessageQueue is empty or there are no messages to process at the moment. So the Looper waits, blocks, and the thread never finishes. When we exit Looper, the next method will return NULL and Looper will end.

Also, since Looper is logic running on a different thread, its dispatchMessage method is also running on a different thread, which is the purpose of thread switching.

  • Quit /quitSafely: Quit Looper

    Quit is to quit the Looper directly, and quitSafely is to quit the MessageQueue without waiting until the message is processed. Look at the code:

public void quit(a) {
    mQueue.quit(false);
}
// This method is always called
void quit(boolean safe) {
 // If you cannot exit, an exception is thrown. This value is assigned when Looper is initialized  if(! mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit.");  }   synchronized (this) {  // Exit once and cannot run again  if (mQuitting) {  return;  }  mQuitting = true;  // Implement different methods  if (safe) {  removeAllFutureMessagesLocked();  } else {  removeAllMessagesLocked();  }  / / wake MessageQueue  nativeWake(mPtr);  } } Copy the code

You can see that the quitSafely method was called at the end. This method determines whether it can exit and then performs the exit logic. If McOntract ==true, then this is the only method we see that the McOntract variable is assigned here, so once looper exits, it can’t run again. A different exit logic is then performed. RemoveAllMessagesLocked () directly removes all messages, which execute at the current time or earlier, and then removes the remaining messages that need to be delayed. The relevant source code is limited to space here is not shown, readers can view the source code. MessageQueue’s next method exits, Looper’s loop method follows, and the thread stops.

Which summarizes

Looper acts as the “power engine” for the Handler message mechanism, continuously retrieving messages from MessageQueue and handing them to Handler for processing. Looper is used by initializing the Looper object of the current thread and then calling the loop method to start it.

Handler is also at the heart of the switch, because different Loopers are running on different threads and the dispatchMessage method is running on different threads, so Message processing is switched to the same thread as the Looper. When looper is no longer in use, different exit methods can be called to exit it. Note that once looper exits, the thread terminates.

The last

This article introduces three very important classes that are part of the Handler mechanism. The remaining Handler key classes are covered in the next section.

Hope this article is helpful.

Full text here, the original is not easy, feel help can like collection comments forward. I have no talent, any ideas welcome to comment area exchange correction. If need to reprint please private communication.

And welcome to my blog: Portal