Content of this article:

  1. Handler Related Issues
  2. Handler Sends messages
  3. Handler processes the message flow
  4. IdleHandler
  5. Asynchronous messaging and synchronization barriers

Problems related to handlers

  1. Handler thread communication mechanism?
  2. What is the maximum number of handlers in a thread, Looper, MessageQueue?
  3. Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
  4. After Looper blocks, who wakes it up, and in what circumstances does the Looper thread wake up?
  5. How does the Handler thread switch?
  6. Handler message priority?

This section describes the principles of the Handler communication mechanism

At the heart of the Handler communication mechanism is the producer-consumer model. To understand this model, take a very simple real-world example: If many people buy something in a supermarket (the producer, in this case, generates a bill), the purchase will generate a bill (message), and assume that there is only one cashier (consumer, the cashier pays the bill), then there will be a checkout queue (message queue). When there is a purchase to checkout (a message), the cashier will checkout the bills one by one (consumption message), and when there is no one to checkout (message queue empty), the cashier can rest (thread sleep).

This is a very common model, usually used with multiple threads producing (producer thread) and one thread consuming (consumer thread). Handler is a typical application of this model, and what about the roles in Handler

Roles in Handler

As you can see from the producer-consumer model above, this model has four roles

  1. The producer, the role that produced the message, corresponds toHandlerThe process of generating the message isHandler.sendMessage()methods
  2. The consumer, the role consuming the message, corresponds toLooperThe process of consuming messages is calledLooper.loop()methods
  3. The message corresponds toMessage
  4. Message queue, which corresponds toMessageQueue

And then we can answer two questions

  1. What is the maximum number of handlers in a thread, Looper, MessageQueue?

    A: a thread can have n handlers (producers can have more than one), but only one Looper(consumer) and MessageQueue(MessageQueue)

  2. Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?

    A: Because when the message queue is empty, the Looper thread blocks until a qualified message is generated

The Handler class diagram

Handler Sends messages

Take a look at Handler usage before looking at the sending message flow

Handler handler;
new Thread(()->{
	Looper.prepare();
  handler = new Handler(Looper.myLooper()){
    @Override
    public void handleMessage(Message msg) {
      Log.d("jonny"."handleMessage: "+ msg.what); }};synchronized (MainActivity.this) {
    notifyAll();
  }

  Looper.loop();
}).start();
synchronized (this) {
  while (null == handler) {
    try {
      wait();
    } catch (InterruptedException e) {
    }
  }
}
handler.sendEmptyMessage(0);
Copy the code

Sending a message to a child thread may seem like a difficult task, but an easier way to do this is to use a HandlerThread, which simplifies the process of creating a Handler for a child thread

HandlerThread thread = new HandlerThread("thread-0");
thread.start();
Handler handler = new Handler(thread.getLooper());
handler.sendEmptyMessage(0);
Copy the code

Looper.prepare()

Create a Looper object and set the reference to a ThreadLocal. ThreadLocal doesn’t actually hold the Looper object, it transfers it to ThreadLocalMap with Thread as key. Looper stores value and is owned by threads rather than ThreadLocal, which feels more like a convenient interface

Looper initialization (consumer)

Create the MessageQueue MessageQueue in the constructor

MessageQueue initialization (MessageQueue)

The native layer Handler and Looper are initialized in the constructor

Handler initialization (producer)

  1. Try to callLooper.myLooper()Gets the Looper object for the current thread. If it does not get the object, it has not been created and a runtime exception is thrown
  2. Get the MessageQueue MessageQueue from the Looper object
  3. For async, you can mark the Handler as a VIP to process their messages first. VIP is not open to developers

Handler.enqueueMessage()

All Handler methods for sending messages eventually converge to enqueueMessage

  1. Set Message’s target object to the current HandlerMessage.target = thisSo that Looper can know who to give the information to later
  2. If the current Handler is a VIP, mark Message as a VIP and callMessage.setAsynchronous()Flags MessageFLAG_ASYNCHRONOUS
  3. callMessageQueue.enqueueMessage()Insert into the message queue

MessageQueue.enqueueMessage()

  1. Check if msg.target is null and throw an exception if so
  2. Add locks to prevent concurrency from causing insert order problems and even loops
  3. To determine whether a Message is being used, there are several cases where it is marked as used
    1. Insert into MessageQueue, as described below
    2. callMessage.postSyncBarrier()When inserting the synchronization barrier, because the action is not throughequeueMessage()methods
    3. inMessageQueue.next()This is a bit confusing when you get Message because the above two actions already mark Message in MessageQueue
  4. If you are exiting, return false, indicating that the message failed to be sent
  5. callMessage.markInUse()Mark Message asFLAG_IN_USE
  6. Set when to delay sending a Message to a Looper(consumer), and then use a temporary variablepTo operate on the Message queue
  7. If there is no Message in the Message queue or if the time of the queue header is less than the current Message time, the current Message is used as the queue header. One other thing is to flag whether or not you need to wake up, which I’ll talk about later
  8. Otherwise, the Message queue is iterated, finding the first Message whose time is larger than the current message.when, and inserting it in front of it, or at the end of the queue. Message queues in MessageQueue are sorted by when, which we’ll keep in mind later in looper.loop ()
  9. ifneedWakeFlag true to wake up the Looper thread (consumer thread)

There are two cases where the Looper thread needs to be woken up in enqueueMessage(), and another case where the wake is cancelled

  1. While Message is currently in the queue header, if the Looper thread is blocked (mBlocked is true)
  2. The second case of arousal has three conditions
    1. The Looper thread is blocking
    2. p.target == nullRepresents that the queue header is a synchronization barrier
    3. The current Message is asynchronous, that is, a Message sent by a VIP Handler
  3. When there is an asynchronous Message before the current asynchronous message.when, indicating that there is no need to wake up

Handler processes the message flow

The last call to the looper.loop () method in our demo is where the consumer thread gets the message

Looper.loop()

  1. callmyLooper()Gets the Looper object from the current thread, which is a static method, before fetching from sThreadLocalprepare()Method to set the Looper object,This also prevents other threads from calling the looper.loop () method through the Looper object
  2. Determine if it is called multiple timesLooper.loop()Method, which means manual wake up, how does that work
  3. Set up themInLoopTrue and gets the MessageQueue object of the current Looper object
  4. Create a loop
  5. callMessageQueue.next()methods
  6. If the Message queue in MessageQueue is empty, it will block instead of returning null Mssage
  7. Here are some performance tracking logs
  8. callHandler.dispatchMessage(), rememberHandler.enqueueMessage()willMessage.target = thisMessage is now passed back to Handler
  9. The last callMessage.recycleUnchecked()Resets the Message so that it can be retrieved later without recreating the Message

MessageQueue.next()

  1. Set up thenextPollTimeoutMillis = 0 pendingIdleHandlerCount = -1Keep these two values in mind and we’ll talk about them later
  2. callnativePollOnce()Block, and the consumer thread will block here unless there are two wake up cases discussed earlier, or a timeout, or when the synchronization barrier is removed
  3. If the queue header is a synchronization barrier, that is, of the queue headerMessage.target == null, there is an asynchronous (VIP) message, try to find the asynchronous message
  4. ifnow < msg.whenIt means it’s not time to go back to sleep, and there are two situations
    1. There is a synchronization barrier and the when time for asynchronous messages has not yet reached
    2. The MessageQueue in MessageQueue is sorted according to when, so we only need to determine whether the message in the queue header has arrived, because the other when times are longer than the time in the queue header
  5. Otherwise, the message is removed (asynchronous message or queue header message) and the MSG found is returned
  6. Otherwise, if there is no message in the queue or there is a synchronization barrier but no asynchronous message, it indicates that there is no message that meets the condition. Set nextPollTimeoutMillis to -1, which means that the queue is blocked permanently until the producer thread wakes up
  7. Is called if exitingdispose()Release the native code resource and return NULL
  8. Get the current IdleHandler under two conditions
    1. pendingIdleHandlerCount < 0This situation representsLooper.loop()When called inside, instead of being reawakened in the next() method
    2. No message returned. Prepare to block
  9. judgependingIdleHandlerCount <= 0If there is no IdleHandler, block directly
  10. Creates an array of idleHandlersmPendingIdleHandlers, the minimum size is 4
  11. IdleHandler to be addedmIdleHandlerstomPendingIdleHandlersAn array of
  12. traversemPendingIdleHandlersIdleHandler in thequeueIdle()Method and get the return valuekeepIf it is false, it is changed frommIdleHandlersRemove the
  13. willpendingIdleHandlerCountandnextPollTimeoutMillisSet it to 0,
    1. willpendingIdleHandlerCountThe reason for setting it to 0 is to prevent the next timenext()IdleHandler is called again when there is no matching message.
    2. willnextPollTimeoutMillisThe reason for setting this to 0 is that a qualified message may be inserted after executing the IdleHandler

Handler.dispatchMessage()

There is a distribution message priority in this method

  1. Check whether Message sets callback(type Runnable)Message.callback.run()
  2. Check whether the Handler has mCallback(Callback) set if thisCallback.handleMessage()Return true, then it is intercepted
  3. The last isHandler.handleMessage()Call, there are two conditions
    1. Message does not set callback
    2. Handler does not set mCallback or sets butCallback.handleMessage()Returns false

IdleHandler

A couple of questions about IdleHandler

  1. How to use IdleHandler, as shown in the following code, IdleHandler must be added to the Looper thread

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override
      public boolean queueIdle(a) {
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        Log.e("Test"."IdleHandler1 queueIdle");
        return false; }});Copy the code
  2. When does IdleHandler call back, and only on the first occurrence of a message that does not meet the criteria

    1. Synchronous barriers have been inserted, but asynchronous messages have not yet been inserted
    2. No message in queue, queue headermMessages == null
    3. Queue header or asynchronous message when time is greater than the current time,

    If all three of these conditions occur, the loop in the next() method continues and blocks. If the blocking is awakened and the IdleHandler does not execute again, Looper.loop() calls messagequeue.next () and resets pendingIdleHandlerCount to -1 until the Message is returned. Note that the pendingIdleHandlerCount is set to 0 after executing IdleHandler

    Midlehandler.size () is not retrieved when pendingIdleHandlerCount == 0, but only when pendingIdleHandlerCount is -1
    if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
      pendingIdleHandlerCount = mIdleHandlers.size();
    }
    / / will directly blocking pendingIdleHandlerCount = = 0
    if (pendingIdleHandlerCount <= 0) {
      // No idle handlers to run. Loop and wait some more.
      mBlocked = true;
      continue;
    }
    Copy the code
  3. Why not just use mIdleHandlers instead of making them an array? MIdleHandlers. Size () changes after the remove() method is called

Synchronous barriers and asynchronous messages

  1. Asynchronous messages are equivalent to VIP messages that need Looper to get first, while synchronization barriers are a mark that opens the VIP Message channel and are itself a Message, but the target of a common Message must be a normal Handler. MSG. Target == null (MSG. Target == null) if there are asynchronous messages in the MessageQueue MessageQueue, try to obtain the asynchronous message from the MessageQueue. The specific steps have been described above

  2. A good example of the use of synchronous barriers and asynchronous messages is Choreographer and ViewRootImpl for UI refresh messages

    //ViewRootImpl.scheduleTraversals()
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    mChoreographer.postCallback(
      Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    //Choreographer.postCallbackDelayedInternal()
    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
    msg.arg1 = callbackType;
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, dueTime);
    //ViewRootImpl.doTraversal()
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    Copy the code

It can be divided into three steps:

  1. Insert synchronization barrier
  2. Sending asynchronous messages
  3. Process asynchronous messages, and then remove the synchronization barrier

The order of these three steps cannot be changed, or asynchronous messages may be processed as synchronous messages

MessageQueue.postSyncBarrier()

  1. Create Message and set message. arg1 to token, which followsremoveSyncBarrier()To prepare
  2. Inserts the synchronization barrier message into the message queueenqueueMessage()Again, keep the message queue sorted by when

Instead of a wake up Looper thread (consumer thread), an attempt is made to wake up when an asynchronous message is inserted

Sending asynchronous messages

Two ways:

  1. Creating a Handler callnew Handler(true)Set to asynchronous so that every message sent by the Handler is asynchronous
  2. callMessage.setAsynchronous(true)Set a single message to be asynchronous

Asynchronous message transmission process is the same as the synchronous message, back to the MessageQueue. EnqueueMessage relevant logic (), pay attention to the change of the asynchronous messaging needWake

MessageQueue.removeSyncBarrier()

  1. A synchronization barrier with the same token is first found in the queue, and an exception is reported if none is found
  2. If the synchronization barrier is not a queue head, there is no need to wake up because the queue head is a synchronization message and is woken up by a timeout
  3. In the case of queue headers, there are two conditions that can cause wakeup
    1. mMessages == nullDo not know why to wake up in this situation
    2. The Message at the head of the queue is not a synchronization barrier (message.target! = null)

conclusion

Some of the answers are not obvious, so let’s summarize the answers to the previous questions

  1. After Looper blocks, who wakes it up, and in what circumstances does the Looper thread wake up?

    A: There are four ways to wake up the Looper thread

    1. The inserted message when value is the smallest in the queue, that is, the message is inserted directly into the queue header
    2. Asynchronous messages are inserted, and the head of the queue is the synchronization barrier
    3. Another special case is that if there are no synchronous messages in the queue, or if there are synchronous barriers but no asynchronous barriers, it will block permanently until it is woken up.nextPollTimeoutMillis = -1In the case
    4. Wake up when the synchronization barrier is removed if the message queue is empty or there are synchronization messages
  2. How does the Handler thread switch?

    Answer: The first thing to note is that thread execution is method-based, independent of objects. In the producer consumer model, there are normally two kinds of threads, producer thread and consumer thread, and producer thread and consumer thread can be the same thread, and for Handler, Handler is producer, Looper is consumer, so

    1. Handler.enqueueMessage() MessageQueue.enqueueMessage()Both are executed in the producer thread
    2. Looper.loop() MessageQueue.next()As well asHandler.dispatchMessage()Is executed in the consumer thread

    A thread switch is a wake up process

  3. Handler message priority?

    See analysis of handler.dispatchMessage () for details