Content of this article:
- Handler Related Issues
- Handler Sends messages
- Handler processes the message flow
- IdleHandler
- Asynchronous messaging and synchronization barriers
Problems related to handlers
- Handler thread communication mechanism?
- What is the maximum number of handlers in a thread, Looper, MessageQueue?
- Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
- After Looper blocks, who wakes it up, and in what circumstances does the Looper thread wake up?
- How does the Handler thread switch?
- 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
- The producer, the role that produced the message, corresponds to
Handler
The process of generating the message isHandler.sendMessage()
methods - The consumer, the role consuming the message, corresponds to
Looper
The process of consuming messages is calledLooper.loop()
methods - The message corresponds to
Message
- Message queue, which corresponds to
MessageQueue
And then we can answer two questions
-
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)
-
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)
- Try to call
Looper.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 - Get the MessageQueue MessageQueue from the Looper object
- 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
- Set Message’s target object to the current Handler
Message.target = this
So that Looper can know who to give the information to later - If the current Handler is a VIP, mark Message as a VIP and call
Message.setAsynchronous()
Flags MessageFLAG_ASYNCHRONOUS
- call
MessageQueue.enqueueMessage()
Insert into the message queue
MessageQueue.enqueueMessage()
- Check if msg.target is null and throw an exception if so
- Add locks to prevent concurrency from causing insert order problems and even loops
- To determine whether a Message is being used, there are several cases where it is marked as used
- Insert into MessageQueue, as described below
- call
Message.postSyncBarrier()
When inserting the synchronization barrier, because the action is not throughequeueMessage()
methods - in
MessageQueue.next()
This is a bit confusing when you get Message because the above two actions already mark Message in MessageQueue
- If you are exiting, return false, indicating that the message failed to be sent
- call
Message.markInUse()
Mark Message asFLAG_IN_USE
- Set when to delay sending a Message to a Looper(consumer), and then use a temporary variable
p
To operate on the Message queue - 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
- 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 ()
- if
needWake
Flag 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
- While Message is currently in the queue header, if the Looper thread is blocked (mBlocked is true)
- The second case of arousal has three conditions
- The Looper thread is blocking
p.target == null
Represents that the queue header is a synchronization barrier- The current Message is asynchronous, that is, a Message sent by a VIP Handler
- 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()
- call
myLooper()
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 - Determine if it is called multiple times
Looper.loop()
Method, which means manual wake up, how does that work - Set up the
mInLoop
True and gets the MessageQueue object of the current Looper object - Create a loop
- call
MessageQueue.next()
methods - If the Message queue in MessageQueue is empty, it will block instead of returning null Mssage
- Here are some performance tracking logs
- call
Handler.dispatchMessage()
, rememberHandler.enqueueMessage()
willMessage.target = this
Message is now passed back to Handler - The last call
Message.recycleUnchecked()
Resets the Message so that it can be retrieved later without recreating the Message
MessageQueue.next()
- Set up the
nextPollTimeoutMillis = 0
pendingIdleHandlerCount = -1
Keep these two values in mind and we’ll talk about them later - call
nativePollOnce()
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 - If the queue header is a synchronization barrier, that is, of the queue header
Message.target == null
, there is an asynchronous (VIP) message, try to find the asynchronous message - if
now < msg.when
It means it’s not time to go back to sleep, and there are two situations- There is a synchronization barrier and the when time for asynchronous messages has not yet reached
- 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
- Otherwise, the message is removed (asynchronous message or queue header message) and the MSG found is returned
- 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
- Is called if exiting
dispose()
Release the native code resource and return NULL - Get the current IdleHandler under two conditions
pendingIdleHandlerCount < 0
This situation representsLooper.loop()
When called inside, instead of being reawakened in the next() method- No message returned. Prepare to block
- judge
pendingIdleHandlerCount <= 0
If there is no IdleHandler, block directly - Creates an array of idleHandlers
mPendingIdleHandlers
, the minimum size is 4 - IdleHandler to be added
mIdleHandlers
tomPendingIdleHandlers
An array of - traverse
mPendingIdleHandlers
IdleHandler in thequeueIdle()
Method and get the return valuekeep
If it is false, it is changed frommIdleHandlers
Remove the - will
pendingIdleHandlerCount
andnextPollTimeoutMillis
Set it to 0,- will
pendingIdleHandlerCount
The reason for setting it to 0 is to prevent the next timenext()
IdleHandler is called again when there is no matching message. - will
nextPollTimeoutMillis
The reason for setting this to 0 is that a qualified message may be inserted after executing the IdleHandler
- will
Handler.dispatchMessage()
There is a distribution message priority in this method
- Check whether Message sets callback(type Runnable)
Message.callback.run()
- Check whether the Handler has mCallback(Callback) set if this
Callback.handleMessage()
Return true, then it is intercepted - The last is
Handler.handleMessage()
Call, there are two conditions- Message does not set callback
- Handler does not set mCallback or sets but
Callback.handleMessage()
Returns false
IdleHandler
A couple of questions about IdleHandler
-
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
-
When does IdleHandler call back, and only on the first occurrence of a message that does not meet the criteria
- Synchronous barriers have been inserted, but asynchronous messages have not yet been inserted
- No message in queue, queue header
mMessages == null
- 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
-
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
-
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
-
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:
- Insert synchronization barrier
- Sending asynchronous messages
- 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()
- Create Message and set message. arg1 to token, which follows
removeSyncBarrier()
To prepare - Inserts the synchronization barrier message into the message queue
enqueueMessage()
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:
- Creating a Handler call
new Handler(true)
Set to asynchronous so that every message sent by the Handler is asynchronous - call
Message.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()
- A synchronization barrier with the same token is first found in the queue, and an exception is reported if none is found
- 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
- In the case of queue headers, there are two conditions that can cause wakeup
mMessages == null
Do not know why to wake up in this situation- 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
-
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
- The inserted message when value is the smallest in the queue, that is, the message is inserted directly into the queue header
- Asynchronous messages are inserted, and the head of the queue is the synchronization barrier
- 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 = -1
In the case - Wake up when the synchronization barrier is removed if the message queue is empty or there are synchronization messages
-
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
Handler.enqueueMessage()
MessageQueue.enqueueMessage()
Both are executed in the producer threadLooper.loop()
MessageQueue.next()
As well asHandler.dispatchMessage()
Is executed in the consumer thread
A thread switch is a wake up process
-
Handler message priority?
See analysis of handler.dispatchMessage () for details