The Handler synchronization barrier mechanism is a relatively advanced and complex feature in Android development, which is mainly used to control the processing order of messages in the message queue MessageQueue. When the synchronization barrier is set, the processing of all normal messages (synchronous messages) will be blocked, while allowing immediate messages (such as messages with callbacks or Runnable objects) to continue to execute. 「Message Category」: - "Normal message (synchronous message)": Common messages sent through Handler are queued in MessageQueue in timestamp order. The messages we usually send are basically synchronous messages, which will not be discussed here.
- "Barrier message (synchronization barrier)": A special Message object with no target attribute, used to insert a barrier in a MessageQueue.
- "Asynchronous messages": messages that can be marked in a specific way, have a higher priority than synchronous messages, and can be processed even if there is a synchronization barrier.
Barrier Messages (Synchronization Barriers) The synchronization barrier is enabled 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) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } - The first step is to obtain the unique identifier of the barrier, which starts at 0 and increments by 1.
- The second step is to get a msg from the Message object pool, set the msg to be in use, and reset the when and arg1 of the msg. The value of arg1 is set to the token value. However, there is no value assigned to tareget here. Therefore, whether the target of msag is empty is a sign to determine whether this msg is a barrier message.
- The third step is to create variables pre and p to prepare for the next step. p is assigned the value of mMessages, which points to the first element in the message queue, so p now points to the first element in the message queue.
- The fourth step is to determine the position of the barrier message in the entire message queue by comparing the when of the first Message in the queue with the when of the barrier, because the messages in the message queue are sorted by time.
- Step 5, prev != null, which 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, and msg is inserted into the head of the message.
Usually, messages are sent through Handler handler.sendMessage(), which will eventually call the enqueueMessage() method in Handler.java. private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); 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() method also gets a msg from the Message object pool and inserts it into the message queue. The only difference is that the target field is not set. From the code level, a barrier message is a Message with an empty target. "Working Principle": The message processing of Handler is to obtain messages from the message queue in the Looper.loop() method and hand them over to the Handler for processing, where the MessageQueue obtains messages through the next method. Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } 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(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { 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. mBlocked = false; 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 msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } if (mQuitting) { dispose(); return null; } if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } } When msg.target == null, it means that the msg at this time is a barrier message. At this time, the loop will be entered to traverse the position of the moving msg until the moved msg is an asynchronous message and exit the loop. That is to say, the loop code will filter out all synchronous messages until the asynchronous message is taken out. When the synchronization barrier is set, the next function will ignore all synchronous messages and return asynchronous messages. After the synchronization barrier is set, the Handler will only process asynchronous messages. The synchronization barrier adds a simple priority mechanism to the Handler message mechanism, and the priority of asynchronous messages is higher than that of synchronous messages. "Remove the barrier": The barrier will not be removed automatically, you need to manually call the MessageQueue.removeSyncBarrier(int token) method to remove it. Token is the unique identifier returned by the postSyncBarrier() method. 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; // 循环遍历,直到遇到屏障消息时推退出循环while (p != null && (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) { // 删除屏障消息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 && !mQuitting) { nativeWake(mPtr); } } } 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: p.target == null (indicating that it is a barrier message) and p.arg1 == token (indicating that p is a barrier message, and msg.arg1 = token was set when the barrier message was queued). After finding the barrier message, delete it from the message queue and recycle it. Asynchronous Messaging Usually we use Handler to add Messages to the message queue synchronously. If we want to add an asynchronous Message, there are two ways: - The Handler constructor has an async parameter. The default constructor parameter is false. Just set the parameter to true when constructing the handler object.
public Handler(@Nullable Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; mIsShared = false; } After async is set to true, set the global mAsynchronous to true. Then call msg.setAsynchronous(true) in enqueueMessage() to set the message to asynchronous. private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } - When creating a Message object, call the setAsynchronous() method of Message. In general, there is no difference between asynchronous messages and synchronous messages, but there is a difference after the synchronization barrier is enabled.
- When Looper takes a message from the MessageQueue for processing, if it encounters a barrier message, it skips all subsequent normal messages until an asynchronous message is found or the barrier is removed.
- Asynchronous messages are not affected by synchronization barriers and can be processed directly.
Application Scenario- "Ensure that immediate tasks are processed first": When certain urgent tasks need to be performed first, a synchronization barrier can be used to temporarily block the processing of other messages.
- "Avoid deadlock and resource competition": In complex message interaction scenarios, using synchronization barriers can prevent deadlock or resource competition caused by improper message processing order.
- 「UI drawing optimization」: In the Android application framework, in order to respond to UI refresh events faster, ViewRootImpl uses a synchronization barrier mechanism in the drawing process to ensure that asynchronous drawing tasks can be executed first.
Precautions- "Use with caution": Improper use of synchronization barriers may cause delays or blocking of message processing, affecting application performance and responsiveness.
- "Manual removal": After using the synchronization barrier, it must be removed manually, otherwise the synchronization message cannot be processed.
|