Android Handler synchronization barrier mechanism

There are three types of Messgae for Handler:

  • General news
  • asynchronous messages
  • barrier message

Asynchronous messages

Usually we use Handler to add Messages to the message queue synchronously. If we want to add an asynchronous Message, there are two ways:

  1. The constructor of Handler has an async parameter. The default constructor parameter is false. As long as we set this parameter to true when constructing the handler object, it will be fine.
 public Handler(Callback callback, boolean async) {<!-- -->
       ...code omitted
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

As you can see, after async is set to true, the global mAsynchronous is set to true. Then in the enqueueMessage() method, call msg.setAsynchronous(true) to set the message to asynchronous.

  1. When creating a Message object, directly call the setAsynchronous() method of Message

Under normal circumstances, there is no difference between asynchronous messages and synchronous messages, but there is a difference once the synchronization barrier is turned on.

Synchronization barrier

Generally speaking, all Messages in the MessageQueue are ordered from front to back according to time.

Synchronizing barrier messages means inserting a barrier into the message queue. All ordinary messages after the barrier will be blocked and cannot be processed. However, asynchronous messages are an exception. The barrier will not block asynchronous messages. Therefore, it can be considered that the barrier message is to ensure the priority of asynchronous messages. After setting the barrier, only subsequent asynchronous messages can be processed. Synchronous messages will be blocked unless revoked. barrier.

The synchronization barrier is turned on through the postSyncBarrier method of MessageQueue.

private int postSyncBarrier(long when) {<!-- -->
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {<!-- -->
            // 1
        final int token = mNextBarrierToken + + ;
        // 2
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
                // 3
                //Point to the previous Message
        Message prev = null;
        //The first Message in the message queue is assigned to p
        Message p = mMessages;
        if (when != 0) {<!-- -->
        // 4 Determine the location where the barrier message is inserted through the time of p and the time of the barrier.
            while (p != null & amp; & amp; p.when <= when) {<!-- -->
                prev = p;
                p = p.next;
            }
        }
        // 5 indicates that the barrier message is not inserted into the head of the message queue
        if (prev != null) {<!-- --> // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {<!-- -->
        // 6 The barrier message is at the head of the message queue
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

  1. Get the unique identifier of the barrier. The identifier starts from 0 and increases by 1.
  2. Get a msg from the Message message object pool, set msg to the in-use state, reset the when and arg1 of msg, and set the value of arg1 to the token value. But target is not assigned a value here. Therefore, whether the target of msg is empty is a sign to determine whether this msg is a barrier message.
  3. Create variables pre and p in preparation for the next step. p is assigned mMessages, mMessages points to the first element in the message queue, so p points to the first element in the message queue at this time.
  4. By comparing the when of the first Message in the queue and the when of the barrier, the position of the barrier message in the entire message queue is determined, because the messages in the message queue are sorted by time. The fifth step, prev != null, means it is not the header of the message, insert msg into the message queue. Step 6, prev == null, which means it is the head of the message queue, insert msg into the head of the message.

We usually send messages through Handler handler.sendMessage(), and eventually call the enqueueMessage() method in Handler.java.

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {<!-- -->
    msg.target = this;
    if (mAsynchronous) {<!-- -->
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

As you can see, the target field is set for msg in the enqueueMessage() method. The postSyncBarrier() above also obtains an msg from the Message message object pool and inserts it into the message queue. The only difference is that the target field is not set. So from a code level, a barrier message is a Message with an empty target.

How barrier messages work

The barrier is inserted into the message queue through the postSyncBarrier method. So how does the barrier block ordinary messages and only allow asynchronous messages to pass?

We know that the Handler’s message processing is to obtain the message from the message queue in Looper.loop() and hand it over to the Handler for processing. The message is obtained through the MessageQueue and the next method. Check out the source code of next(),

Message next() {<!-- -->
   // .....omit code
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {<!-- -->
        if (nextPollTimeoutMillis != 0) {<!-- -->
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {<!-- -->
            // Try to retrieve the next message. Return if found.
            final long now = SystemClock.uptimeMillis();
            // Point to the previous message
            Message prevMsg = null;
            //Initially points to the first message
            Message msg = mMessages;
            // 1 msg.target == null indicates that a message barrier was encountered
            if (msg != null & amp; & amp; msg.target == null) {<!-- -->
                    // Being able to enter this if means that the msg at this time is a barrier message
                    // Loop traversal, the condition for exiting the loop is that the message reaches the end, or
                    // msg is an asynchronous message
                do {<!-- -->
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null & amp; & amp; !msg.isAsynchronous());
            }
   
}

if (msg != null) {<!-- -->
    if (now < msg.when) {<!-- -->
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {<!-- -->
          // Got a message.
          mBlocked = false;
          if (prevMsg != null) {<!-- -->
          // Remove msg from the message list
                 prevMsg.next = msg.next;
              } else {<!-- -->
                mMessages = msg.next;
              }
            msg.next = null;
           if (DEBUG) Log.v(TAG, "Returning message: " + msg);
            msg.markInUse();
            //return msg
            return msg;
         }

As can be seen from the above code, when msg.target == null, it means that the msg at this time is a barrier message. At this time, it will enter a loop, traverse the position of the moved msg, and exit the loop when it knows that the moved msg is an asynchronous message. That is to say, the loop code will filter out all synchronous messages until the asynchronous message is taken out.

When a synchronization barrier is set, the next function will ignore all synchronous messages and return asynchronous messages. In other words, after setting the synchronization barrier, the Handler will only process asynchronous messages. In other words, the synchronization barrier adds a simple priority mechanism to the Handler message mechanism, and asynchronous messages have a higher priority than synchronous messages.

Remove sync barrier

The synchronization barrier is removed in the removeSyncBarrier() method of MessageQueue.java.

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;
        // Loop through until a barrier message is encountered and push out of the loop
        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) {<!-- -->
        // Delete barrier message p
            prev.next = p.next;
            needWake = false;
        } else {<!-- -->
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // 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 removeSyncBarrier method needs to pass in a parameter token, which can be obtained from the return value of the postSyncBarrier adding barrier method.

The method of deleting barrier messages is very simple, which is to continuously traverse the message queue until the barrier message is found. There are two conditions for exiting the loop. One is p.target == null, indicating that it is a barrier message, and the other is p.arg1 == token. It also shows that p is a barrier message, because msg.arg1 = token was set when the barrier message was enqueued. After the barrier message is found, it is removed from the message queue and recycled.

Where are barrier messages used?

The system marks things such as inserting barriers and constructing asynchronous Handlers as @UnsupportedAppUsage, which means that these APIs are used by the system itself and do not want developers to call them. When was the system used?

Asynchronous messages require the assistance of synchronization barriers, but we cannot add synchronization barriers manually, so it is necessary to understand when the system adds and removes synchronization barriers. Only in this way can we better run the asynchronous messaging function and know why and how to use it. Understanding synchronization barriers requires a brief understanding of the screen refresh mechanism.

There are different types of mobile phone screen refresh screens, 60Hz, 120Hz, etc. The screen will send out a Vsync signal every time it is refreshed to notify the CPU to perform drawing calculations. Specifically in our code, it can be thought of as executing onMeasure, onLayout, and onDraw methods.

The starting point of View drawing is ViewRootImpl’s requestLayout(). This method will perform the above three major drawing tasks: measurement, layout, and drawing. After calling the requestLayout() method, the drawing task will not start immediately. Instead, a synchronization screen will be set for the main thread and Vsync signal monitoring will be set. When the Vsync signal arrives, an asynchronous message will be sent to the main thread Handler to execute the drawing listening task we set up in the previous step and remove the synchronization barrier.

//ViewRootImpl.java
@Override
public void requestLayout() {<!-- -->
    if (!mHandlingLayoutInLayoutRequest) {<!-- -->
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
void scheduleTraversals() {<!-- -->
    if (!mTraversalScheduled) {<!-- -->
        mTraversalScheduled = true;
        //Insert barrier
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //Listen to the Vsync signal and then send an asynchronous message -> Execute the drawing task
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {<!-- -->
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

The main thread does nothing while waiting for the Vsync signal. This has the advantage of ensuring that when the Vsync signal arrives, the drawing task can be executed in time without causing interface lag.

In this case, the ordinary messages we send may be delayed. After the Vsync signal arrives, the barrier is removed and the ordinary messages can be processed. The way to improve this problem is to use asynchronous messages. After sending an asynchronous message, our tasks can be executed even while waiting for Vsync, so that the tasks we set can be executed faster (do this only if necessary, UI drawing is high) for everything) and reduce the Looper pressure on the main thread.

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 Major 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 strictly arranged 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