Articles cover a larger image from: www.zacsweers.dev/rxandroids-…
preface
Nice to meet you
Pretty much every Android developer knows about Handler, and there are plenty of good blogs on the web. I wrote a post about it earlier that you might want to check out: Portals.
This article focuses on synchronization barriers in handlers, a popular interview question. Many readers feel that this piece of knowledge is very partial, not useful in actual practice, only for interviews, including the author. I wrote in Handler Mechanisms that synchronization barriers are not really useful for our everyday use. Since both the synchronization barrier and asynchronous Handler methods are labeled hide, Google doesn’t want us to use them.
I was asked this question in an interview some time ago, then rethought it and found some differences. Combined with some of the big guys’ ideas, it turns out that synchronization barriers are not completely useless as we thought, but still have their advantages. This article is about my thoughts on the synchronization barrier mechanism. I hope it will help you.
This article will first introduce what synchronization barriers are and then analyze how to use them and how to use them correctly.
So, let’s get started.
What is the synchronization barrier mechanism
Synchronization barriers are a set of mechanisms designed to make certain messages run faster.
Note that I have added the word “mechanism” after synchronization barriers, because synchronization barriers alone do not work, they need to work in conjunction with other Handler components.
Here we assume a scenario where we send a UI draw operation Message to the main thread, and there are too many messages in the Message queue, the processing of this Message may be delayed and the interface may get stuck if the Message is not drawn in time. The synchronization barrier mechanism allows the draw message to be executed over other messages.
The Message in MessageQueue has a variable isAsynchronous, which indicates whether the Message isAsynchronous. Marked true is called an asynchronous message and marked false is called a synchronous message. There is also another variable, target, which indicates which Handler will process the Message.
When a Message is inserted into a MessageQueue, the target property of the Message must not be null.
MessageQueue.class
boolean enqueueMessage(Message msg, long when) {
// Hanlder cannot be null
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target."); }... }Copy the code
Android provides another way to insert a special message that forces target==null:
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) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
returntoken; }}Copy the code
The code is a bit long, but the point is that Message is not assigned the target attribute and is inserted into the Message queue header. Of course, the source code also involves delayed messages, which we don’t care about for now. This particular Message with target== NULL is the synchronization barrier
Ps: If the synchronization barrier is delayed, instead of being inserted directly into the header, it will process the previous synchronization messages first. But here we just need to understand what a synchronization barrier is.
If a MessageQueue encounters a synchronization barrier while retrieving the next Message, it does not retrieve the synchronization barrier, but instead iterates through subsequent messages to find the first asynchronous Message retrieved and returns. All synchronous messages are skipped and asynchronous messages are executed directly. Why is it called a synchronization barrier? Because it can mask synchronous messages in favor of asynchronous ones.
Let’s see how the source code is implemented:
Message next(a) {...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 that the synchronization barrier will not be removed automatically. You need to remove it manually after using it. Otherwise, synchronization messages cannot be processed. We can take a look at the source code:
Message next(a) {...// Block time
int nextPollTimeoutMillis = 0;
for (;;) {
// Block the corresponding time
nativePollOnce(ptr, nextPollTimeoutMillis);
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 there is a synchronization barrier and no asynchronous message is found,
// MSG will loop to the end of the list, i.e. MSG ==null
if(msg ! =null{...})else {
// There is no message and the state is blocked
nextPollTimeoutMillis = -1; } ···}}}Copy the code
You can see that if the synchronization barrier is not removed immediately, it will remain and no synchronization messages will be executed. Therefore, it must be removed immediately after use. But we don’t have to worry about that. We’ll see.
How do I send asynchronous messages
We saw what synchronization barriers can do, but you can see that the postSyncBarrier method is marked @hide, which means we can’t call it. So, what’s the point of all this?
Ahem ~ don’t panic, but we can send asynchronous messages. While the system adds a synchronization barrier, it’s a good time to get in the car, isn’t it?
There are two ways to add asynchronous messages:
- All messages sent using an asynchronous type Handler are asynchronous
- Flag Message asynchronously
Marking Message asynchronous is relatively simple, using the setAsynchronous method.
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 Handler constructor of the asynchronous type is marked hide and we cannot use it, but two important methods have been 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);
}
Copy the code
These two apis allow you to create asynchronous handlers that send out all messages asynchronously.
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else{ flags &= ~FLAG_ASYNCHRONOUS; }}Copy the code
How to use it correctly
We seem to have missed the question above: when does the system add a synchronization barrier? .
Asynchronous messages need the assistance of synchronization barriers, but synchronization barriers cannot be added manually, so it is essential to know when systems add and remove synchronization barriers. Only in this way can you make better use of asynchronous messaging and know why and how to use it.
Understanding synchronization barriers requires a brief introduction to the screen refresh mechanism. Relax. You just need to know didiu.
Our mobile phone screen refresh frequency has different types, 60Hz, 120Hz and so on. 60Hz means that the screen refreshes 60 times in a second, or every 16.6ms. The screen emits a VSYNC signal each time it refreshes, notifying the CPU to perform the drawing calculation. Specific to our code, can be considered to be the implementation of onMesure(), onLayout(), onDraw() these methods. All right, that’s about it.
Understand the view drawing principle of readers should know that the starting point of the view map is in viewRootImpl requestLayout () method, this method will map to implement the above three tasks, is to measure the layout drawing. But here’s the point:
call
requestLayout()
Instead of immediately starting the drawing task, a synchronization barrier is placed on the main thread and a VSYNC signal listener is set. When the VSYNC signal arrives, an asynchronous message is sent to the main thread Handler, which performs the draw listening task we set up in the previous step and removes the synchronization barrier
The only thing we need to be clear about here is that a synchronization barrier is set up after the requestLayout() method is called and will not be drawn and removed until the VSYNC signal arrives. (There are more details about Android screen refresh and drawing principle, but this article is not detailed. Interested readers can click the link at the end of the article to read.)
So, the main thread is doing nothing while waiting for the VSYNC signal? Yes. The advantage of this is to ensure that when the ASYNC signal arrives, the drawing task can be executed in time without causing interface lag. But there is a cost:
- Our synchronization message may be delayed by at most one frame, or 16ms, before being executed
- The main thread Looper causes too much pressure to concentrate on processing all messages when the VSYNC signal arrives
Ps: [The main thread does nothing while waiting for the VSYNC signal? Yes.] Inserting a synchronization barrier prevents synchronous messages from executing, so that even if there are synchronous messages in the queue, they are not processed, i.e., “nothing is done.” However, the main thread does not “do nothing” if the execution of the previous synchronization task delays the execution of the draw request task, or if there are asynchronous messages in the queue. This is to help readers understand better, at the expense of a little rigor.
The solution to this problem is to use asynchronous messaging. When we send asynchronous messages to MessageQueue, we can also execute our tasks while waiting for VSYNC, allowing our set tasks to be executed faster and with less stress on the main thread Looper.
Some readers may think that the asynchronous messaging mechanism itself is to avoid interface lag, so we directly use asynchronous messaging, is there a danger? Here we need to consider when asynchronous messages can cause interface lag: asynchronous task execution is too long, asynchronous message volume.
It should be easy to understand that if asynchronous messages take too long to execute, the interface will lag even if the task is synchronous. Secondly, if the massive arrival of asynchronous messages affects the interface drawing, even synchronous tasks will lead to interface lag. The reason is that MessageQueue is a linked list structure. Massive messages will reduce the traversal speed and affect the execution efficiency of asynchronous messages. So one thing we should pay attention to is:
You cannot perform heavy tasks on the main thread, either asynchronously or synchronously.
So, shouldn’t we just use asynchronous handlers instead of synchronous handlers? Yes and no.
A synchronization Handler follows the sequence of drawing tasks. After setting synchronization barriers, it waits for drawing tasks to complete before executing synchronization tasks. The order of asynchronous tasks and draw tasks is not guaranteed and may be executed while waiting for VSYNC or after the draw is complete. Therefore, my advice is to use a synchronization Handler if you need to keep things in order with drawing tasks; Otherwise, use asynchronous handlers.
The last
Deep technology digging, always can learn some more different knowledge. As the breadth of knowledge becomes wider, the connections between knowledge will burst out different sparks.
The first time you learned about Handler, you just knew you could send a message and execute it; The second time I learned Handler, I knew its important position in The Android messaging mechanism. The third time we learned about Handler, we learned that there is a connection between Handler and screen refresh mechanism.
Review the old and know the new, the ancients honestly do not deceive me.
If the article is helpful to you, I hope you can give a thumbs-up to encourage the author.
Recommend literature
- Overall Understanding: Feiyang Hu has written a great article on drawing and screen refreshing, which will help you understand asynchronous messaging better.
- RxAndroid 2.1.0 has a New API: An article on RxAndroid’s decision to add an asynchronous API in 2018 explains why asynchronous messaging is used.