“
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 is the final article in a series that focuses on common problems with handlers.
“
The Handler series is divided into six parts:
- Part 1: Start the series with Handler from 0;
- The second part introduces the internal schema of Handler, and elaborates on the key class ThreadLocal.
- The third part: parsing Handler internal key classes: Message, MessageQueue, Looper;
- Part 4: Handler internal key class: Handler, at the same time introduce HandlerThread;
- The fifth part: summarize Handler, think Handler from the perspective of source code design;
- Part 6: Handler FAQ;
This is the sixth part of a series of articles. 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
Why doesn’t the main thread initialize Looper?
A: Because the application initializes the main thread Looper during startup.
Every Java application has a main method entry, and Android java-based applications are no exception. The entry to the Android application is in the ActivityThread main method:
public static void main(String[] args) {
. // Initialize the main thread Looper
Looper.prepareMainLooper();
. // Create an ActivityThread object ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); // Get ActivityThread Handler, which is also its inner class H if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } . Looper.loop(); // If the loop method ends, an exception is thrown and the program ends throw new RuntimeException("Main thread loop unexpectedly exited"); } Copy the code
The main method initializes the main thread Looper, creates a new ActivityThread object, and then starts Looper so that the main thread Looper runs when the program starts. We don’t need to initialize the main thread Looper anymore.
Why is the main thread Looper an infinite loop, but no ANR?
A: Because Looper blocks when it has processed all the messages, it breaks the block when a new Message comes in and continues executing.
That’s not really understanding the concept of ANR. ANR, full name Application Not Responding. When I send a message to the main thread Handler that draws the UI and it is not executed for a certain amount of time, an ANR exception is raised. Looper is a loop that executes various transactions, including UI drawing transactions. Looper dead loop indicates that the thread is not dead. If Looper stops the loop, the thread ends and exits. Looper’s dead-loop is itself one of the things that ensures that the UI drawing task can be performed. At the same time, the UI drawing task has synchronization barrier, which can be faster to ensure that the drawing is executed faster. We’ll see below the synchronization barrier.
How does Handler secure MessageQueue concurrent access?
A: Cyclic lock, with blocking wake up mechanism.
We can see that MessageQueue is actually a producer-consumer model, where handlers keep putting messages in and Looper keeps taking them out, and deadlocks are involved. If Looper gets the lock, but there is no message in the queue, it will wait, and the Handler needs to put the message in, but the lock is held by Looper and cannot join the queue, causing a deadlock. The Handler mechanism works around locking loops. In MessageQueue’s next method:
Message next(a) {
. for (;;) {
. nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { . } } } Copy the code
We can see that his wait is outside the lock. When there is no message in the queue, he will release the lock and wait until he is woken up. This will not cause deadlock problems.
Is it possible to join a queue while holding a lock because the queue is full? The difference here is that MessageQueue’s message has no upper limit, or its upper limit is the amount of memory that the JVM allocates to the program. If it runs out of memory, it will throw an exception, but it usually doesn’t.
How does Handler switch threads?
A: Use loopers of different threads to process messages.
Earlier we talked about the thread of execution of the code, not the code itself, but the thread on which the logic to execute the code is called, or the logic in which the logic is called. Each Looper runs on its own thread, so the dispatchMessage method called by a different Looper runs on its own thread.
What is the blocking wake up mechanism of Handler?
A: Handler’s blocking wake up mechanism is linux-based blocking wake up mechanism.
This mechanism is also similar to the handler mechanism pattern. A file descriptor is created locally, and the waiting party listens for the file descriptor. The wake-up party only needs to modify the file, and the waiting party receives the file to break the wake-up. In much the same way that Looper listens to MessageQueue, Handler adds messages. The specific Linux layer knowledge can be read in detail in this article (Portal)
Can a Message be processed urgently? What is the Handler synchronization barrier?
A: Yes/a mechanism that allows asynchronous messages to be processed faster
If a UI update operation Message is sent to the main thread and there are too many messages in the Message queue, processing of the Message will be slow, causing the interface to stall. So with synchronization barriers, UI drawn messages can be executed faster.
What is a synchronization barrier? The “barrier” is actually a Message inserted in the header of a MessageQueue with target==null. “Message” can’t be null. No, no, no, adding a synchronization barrier is another way:
public int postSyncBarrier(a) {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; // Execute all messages that need to be executed if(when ! =0) { while(p ! =null && p.when <= when) { prev = p; p = p.next; } } // Insert the synchronization barrier if(prev ! =null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } Copy the code
You can see that the synchronization barrier is a special target. What’s special? Target ==null, we can see that he does not assign a value to target. So what does target do? Look at the next method:
Message next(a) {
.
// Block time
int nextPollTimeoutMillis = 0;
for (;;) { . // 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; / * * * 1 * / if(msg ! =null && msg.target == null) { // Synchronization barrier to find the next asynchronous message do { prevMsg = msg; msg = msg.next; } while(msg ! =null && !msg.isAsynchronous()); } 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; / * * * 2 * / // Only asynchronous messages are removed from the middle. Synchronous messages are retrieved from the header 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; } // Call looper.quitsafely () and exit after all the messages are executed if (mQuitting) { dispose(); return null; } . } . } } Copy the code
I mentioned this method earlier. Let’s focus on the synchronization barrier and look at the code in comment 1:
if(msg ! =null && msg.target == null) {
// Synchronization barrier to find the next asynchronous message
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous()); } Copy the code
If a synchronization barrier is encountered, then isAsynchronous will loop through the list to find the Message marked as asynchronous, isAsynchronous will return true, and all other messages will be ignored, and the asynchronous Message will be executed ahead of time. Note the code in comment 2.
Note that the synchronization barrier will not be removed automatically. You need to remove it manually after using it. Otherwise, synchronization messages cannot be processed. It can be seen from the source that if the synchronization barrier is not removed, it will always be there and the synchronization message will never be executed.
With a synchronization barrier, one more condition must be added for wake up: the MessageQueue has a synchronization barrier and is blocking, and a new asynchronous message is inserted before all asynchronous messages. This is also easy to understand, the same as synchronous messages. If all synchronization messages are ignored first, then new list headers are inserted and the queue is blocked, at which point it needs to be woken up. Take a look at the source:
boolean enqueueMessage(Message msg, long when) {
.
// Lock MessageQueue
synchronized (this) {
. if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { / * * * 1 * / // When the thread is blocked, there is currently a synchronization barrier, and the incoming message is asynchronous needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } / * * * 2 * / // If an asynchronous message is found, the asynchronous message with delay needs to be processed and does not need to be woken up if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; prev.next = msg; } // Wake up the queue if necessary if (needWake) { nativeWake(mPtr); } } return true; } Copy the code
Again, this is the same approach I talked about earlier, where you ignore the code irrelevant to the synchronization barrier and see the code in comment 1. If the inserted message is an asynchronous message with a synchronization barrier and the MessageQueue is blocking, you need to wake up. If the insertion position of the asynchronous message is not before all asynchronous messages, there is no need to wake up, as noted in comment 2.
So how do we send an asynchronous type message? There are two ways:
- All messages sent using an asynchronous type Handler are asynchronous
- Flag Message asynchronously
Handler has a set of constructors that take Boolean arguments to determine whether an asynchronous Handler is used:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
// Here is the assignment
mAsynchronous = async; } Copy the code
Message is assigned when the Message is sent:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
/ / assignment
if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } Copy the code
But the asynchronous type Handler constructor is marked hide and we can’t use it, so we use asynchronous messages only by setting the asynchronous flag to Message:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
} } Copy the code
But !!!! In fact, synchronization barriers are not very useful for our daily use. Since both the synchronization barrier and asynchronous Handler methods are labeled hide, Google doesn’t want us to use them. So synchronization barriers are also used as a way to get a more complete understanding of the source code.
What is IdleHandler?
A: Interface object that is called back when MessageQueue is empty or there is currently no Message to execute.
IdleHandler looks like a Handler, but it’s really just a single method interface, also known as a functional interface:
public static interface IdleHandler {
boolean queueIdle(a);
}
Copy the code
There is a List of IdleHandler objects in MessageQueue, and all idleHandlers are called back when MessageQueue has no messages that need to be executed. So IdleHandler is mainly used to do light work when the message queue is idle.
IdleHandler is called in the next method:
Message next(a) {
// If looper has already exited, null is returned
final long ptr = mPtr;
if (ptr == 0) {
return null;
} // The number of idleHandlers int pendingIdleHandlerCount = -1; // 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 && msg.target == null) { // Synchronization barrier to find the next asynchronous message do { prevMsg = msg; msg = msg.next; } while(msg ! =null && !msg.isAsynchronous()); } 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; // Only asynchronous messages are removed from the middle. Synchronous messages are retrieved from the header 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; } // Call looper.quitsafely () and exit after all the messages are executed if (mQuitting) { dispose(); return null; } PendingIdleHandlerCount <0 is given when the queue runs out of messages or both wait time to delay execution PendingIdleHandlerCount specifies the number of idleHandlers in MessageQueue if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // Continue with no IdleHanlder to execute if (pendingIdleHandlerCount <= 0) { // Execute IdleHandler and mark MessageQueue as blocked mBlocked = true; continue; } // Convert List to array type if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } / / IdleHandler execution for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // Release a reference to IdleHandler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // If false is returned, IdleHanlder is removed if(! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Finally set pendingIdleHandlerCount to 0 to prevent a repeat execution pendingIdleHandlerCount = 0; // While executing IdleHandler, new messages may come in // So do not block at this time, go back to loop once nextPollTimeoutMillis = 0; } } Copy the code
A lot of code, may look a bit messy, I comb the logic, and then go back to look at the source will be very clear:
- When the next method is called, it gives
pendingIdleHandlerCount
Assignment to 1 - If there are no messages in the queue that need to be processed
pendingIdleHandlerCount
Whether it is< 0
If so, assign the length of the list storing IdleHandler topendingIdleHandlerCount
- Put all the IDleHandlers in the list into an array. This step is to prevent the List from being inserted into the IdleHandler while executing the IdleHandler, causing logic chaos
- It then iterates through the array and executes all of the IDleHandlers
- The last to
pendingIdleHandlerCount
Assign a value of 0. Then go back and see if any new messages were inserted during this period. becausependingIdleHandlerCount
Is 0 instead of -1, so IdleHandler will only execute once when idle - Also note that if IdleHandler returns false, it is discarded after a single execution.
I suggest readers go back to the source code again, so the logic will be a lot clearer.
The last
This concludes the Handler series of articles. If you have any questions or omissions, please leave them in the comments section. Thanks for reading.
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
“