Android Handler synchronization barrier

What is the synchronization barrier mechanism

The synchronization barrier mechanism is a set of mechanisms to allow certain special messages to be executed faster.
Here we assume a scenario: we send a UI drawing operation Message to the main thread, and there are many messages in the message queue at this time. Then the processing of this Message may be delayed, and the drawing may not be timely, causing the interface to freeze. The function of the synchronization barrier mechanism is to allow this drawing message to bypass other messages and be executed first.

The Message in the MessageQueue has a variable isAsynchronous, which marks whether the Message is an asynchronous message; marking it as true is called an asynchronous message, and marking it as false is called a synchronous message. There is also another variable target, which marks which Handler this Message will eventually be processed by.
From the Handler source code, we know that when each Message is inserted into the MessageQueue, its target attribute will be forced to not be null, as shown in the following code:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {<!-- -->
   // Note here that the target points to the current Handler
     msg.target = this;
 if (mAsynchronous) {<!-- -->
     msg.setAsynchronous(true);
 }
 //The queue#enqueueMessage method is called
 return queue.enqueueMessage(msg, uptimeMillis);

}
Here msg.target will be assigned the value this, and this is our Handler object. Therefore, the target of the message passed in this way is definitely not null, and mAsynchronous defaults to false, which means that the messages we generally send are synchronous messages.
So what is an asynchronous message and how to send an asynchronous message?
Simply put, there are two ways.
One is to directly set the message to be asynchronous:

Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);

There is also a constructor that requires the use of Handler, but this method has been marked @Hide:

public Handler(boolean async) {<!-- -->
     this(null, async);
}

But two important methods were added after api28:

public static Handler createAsync(@NonNull Looper looper) {<!-- -->
    if (looper == null) throw new NullPointerException("looper must not be null");
    return new Handler(looper, null, true);
}

public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {<!-- -->
    if (looper == null) throw new NullPointerException("looper must not be null");
    if (callback == null) throw new NullPointerException("callback must not be null");
    return new Handler(looper, callback, true);
}

Through these two APIs, you can create an asynchronous Handler, and the messages sent by the asynchronous Handler are all asynchronous.

public void setAsynchronous(boolean async) {<!-- -->
  if (async) {<!-- -->
      flags |= FLAG_ASYNCHRONOUS;
  } else {<!-- -->
      flags & amp;= ~FLAG_ASYNCHRONOUS;
  }
}

But without synchronization barriers, there is no difference in the execution of asynchronous messages and synchronous messages.

Synchronization barrier

What exactly does a synchronization barrier do?

The synchronization barrier provides a priority strategy for the handler message mechanism, allowing asynchronous messages to have higher priority than synchronous messages. How to turn on the synchronization barrier?

MessageQueue.postSyncBarrie(), this method will insert a synchronization barrier message into the MessageQueue, without assigning a target attribute to the Message, and insert it into the head of the Message queue. Of course, the source code also involves delayed messages, which we don’t care about for the time being. This special Message with target==null is the synchronization barrier.

When MessageQueue gets the next Message, if it encounters a synchronization barrier, it will not take out the synchronization barrier. Instead, it will traverse subsequent Messages, find the first asynchronous message, take it out and return it. All synchronous messages are skipped here and asynchronous messages are executed directly. Why is it called a synchronization barrier? Because it can block synchronous messages and prioritize asynchronous messages.
The final processing of the message is in the message poller Looper.loop(), and the loop() loop will call MessageQueue.next() to get the message from the message queue. Let’s take a look at the key code.

Message next() {<!-- -->
        for (;;) {<!-- -->
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {<!-- -->
                Message msg = mMessages;
                //If msg.target is empty, which means it is a synchronization barrier message, enter this judgment.
                if (msg != null & amp; & amp; msg.target == null) {<!-- -->
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                    //In this while loop, find the latest asynchronous message
                    //Execute do first, then execute while, so the barrier message will not be taken out
                    do {<!-- -->
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null & amp; & amp; !msg.isAsynchronous());
                }
              
                if (msg != null) {<!-- -->
                    //If the message processing time is greater than the current time, wait
                    if (now < msg.when) {<!-- -->
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {<!-- -->
                        // Got a message.
                        //Process the message
                        mBlocked = false;
                        //Remove the message
                        if (prevMsg != null) {<!-- -->
                            prevMsg.next = msg.next;
                        } else {<!-- -->
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //return message
                        return msg;
                    }
                } else {<!-- -->
                    // No more messages.
                    //If no message is found, it enters the blocking state and waits to be awakened.
                    nextPollTimeoutMillis = -1;
                }
                //...
    }


As can be seen from the above, when a synchronization barrier message is executed (that is, identified as msg.target == null), the message mechanism prioritizes asynchronous messages. Since do is executed first and then while is executed in the code, the pointer points to the next message of the synchronization barrier message for the first time, so the synchronization barrier message will always be in the message queue.
Note that the synchronization barrier will not be automatically removed and needs to be removed manually after use, otherwise the synchronization message will not be processed

Synchronization barrier usage scenarios

We seem to have missed a question above: When does the system add synchronization barriers?
Asynchronous messages require the assistance of synchronization barriers, but we cannot add synchronization barriers manually, so it is very necessary to understand when the system adds and removes synchronization barriers. Only in this way can we make better use of the asynchronous messaging function and know why and how to use it.

Updating the UI in the Android system uses synchronization barriers.
When View is updated, ViewRootImpl#scheduleTraversals() is called in many places such as draw, requestLayout, invalidate, etc., as follows:

//ViewRootImpl.java
    void scheduleTraversals() {<!-- -->
        if (!mTraversalScheduled) {<!-- -->
            mTraversalScheduled = true;
            //Send synchronization barrier message
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //Send asynchronous message
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {<!-- -->
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

Here, synchronization barrier messages are sent, and asynchronous messages are sent. Since messages related to UI updates have the highest priority, the system will prioritize these asynchronous messages.
We saw earlier that the synchronization barrier message will not be removed by itself, and the relevant code needs to be called to remove the synchronization barrier message ViewRootImpl#unscheduleTraversals().

void unscheduleTraversals() {<!-- -->
     if (mTraversalScheduled) {<!-- -->
         mTraversalScheduled = false;
         //Remove synchronization barrier message
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
         mChoreographer.removeCallbacks(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
     }
 }

 /**
  * Removes a synchronization barrier.
  *
  * @param token The synchronization barrier token that was returned by
  * {@link #postSyncBarrier}.
  *
  * @throws IllegalStateException if the barrier was not found.
  *
  */
 public void removeSyncBarrier(int token) {<!-- -->
     // Remove a sync barrier token from the queue.
     // If the queue is no longer stalled by a barrier then wake it.
     synchronized (this) {<!-- -->
         Message prev = null;
         Message p = mMessages;
         //Find the synchronization barrier message
         while (p != null & amp; & amp; (p.target != null || p.arg1 != token)) {<!-- -->
             prev = p;
             p = p.next;
         }
         if (p == null) {<!-- -->
             throw new IllegalStateException("The specified message queue synchronization "
                      + " barrier token has not been posted or has already been removed.");
         }
         final boolean needWake;
         if (prev != null) {<!-- -->
             prev.next = p.next; //next points to the next message
             needWake = false;
         } else {<!-- -->
             mMessages = p.next;
             needWake = mMessages == null || mMessages.target != null;
         }
         p.recycleUnchecked(); //Recycle synchronization barrier messages

         // If the loop is quitting then it is already awake.
         // We can assume mPtr != 0 when mQuitting is false.
         if (needWake & amp; & amp; !mQuitting) {<!-- -->
             nativeWake(mPtr);
         }
     }
 }

The use of synchronization barriers in the drawing process ensures that when the vsync signal arrives, the drawing task can be executed in time to avoid interface freezes. But this also brings corresponding costs:

  1. Our synchronization message may be delayed by up to one frame, which is 16ms, before it is executed.
  2. The main thread Looper causes excessive pressure and only concentrates on processing all messages when the VSYNC signal arrives.

The way to improve this problem is:
Use asynchronous messages. When we send an asynchronous message to the MessageQueue, our tasks can also be executed while waiting for VSYNC, so that the tasks we set can be executed faster and reduce the pressure on the main thread Looper.

Some readers may think that the asynchronous message mechanism itself is to avoid interface lag, so if we use asynchronous messages directly, will there be any hidden dangers? Here we need to think about what situations asynchronous messages will cause interface lag: asynchronous message task execution is too long, asynchronous messages are massive.

If the execution time of an asynchronous message is too long, even if it is a synchronous task, the interface will be stuck. This should be easy to understand. Secondly, if the massive arrival of asynchronous messages affects interface drawing, even synchronous tasks will cause the interface to freeze; the reason is that MessageQueue is a linked list structure, and massive messages will cause the traversal speed to decrease and also affect the execution efficiency of asynchronous messages. . So what we should pay attention to is:

Heavy tasks cannot be performed on the main thread, whether asynchronously or synchronously.

How do we choose to use asynchronous Handler or synchronous Handler in the future?

A characteristic of the synchronization Handler is that it will follow the order of the drawing tasks. After setting the synchronization barrier, it will wait for the drawing task to be completed before executing the synchronization task; the order of the asynchronous task and the drawing task cannot be guaranteed, and it may be blocked while waiting for VSYNC. Executed, possibly after drawing is complete. Therefore, my suggestion is: if you need to ensure the order of drawing tasks, use a synchronous Handler; otherwise, use an asynchronous Handler.

Finally

If you want to become an architect or want to break through the 20-30K salary range, then don’t be limited to coding and business, you must be able to select and expand, and improve your programming thinking. In addition, good career planning is also very important, and learning habits are important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.

If you have no direction, here is a set of “Advanced Notes on the Eight Modules of Android” written by a senior architect at Alibaba to help you systematically organize messy, scattered, and fragmented knowledge, so that you can systematically and efficiently Master various knowledge points of Android development.
img
Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are arranged strictly according to the knowledge system.

Everyone is welcome to support with one click and three links. If you need the information in the article, just scan the CSDN official certification WeChat card at the end of the article to get it for free ↓↓↓ (There is also a small bonus of the ChatGPT robot at the end of the article, don’t miss it)

PS: There is also a ChatGPT robot in the group, which can answer everyone’s work or technical questions

image