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:

  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 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:

  1. When the next method is called, it givespendingIdleHandlerCountAssignment to 1
  2. If there are no messages in the queue that need to be processedpendingIdleHandlerCountWhether it is< 0If so, assign the length of the list storing IdleHandler topendingIdleHandlerCount
  3. 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
  4. It then iterates through the array and executes all of the IDleHandlers
  5. The last topendingIdleHandlerCountAssign a value of 0. Then go back and see if any new messages were inserted during this period. becausependingIdleHandlerCountIs 0 instead of -1, so IdleHandler will only execute once when idle
  6. 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