When reading the Android drawing source code, many articles analyzed that the execution of drawing message must take precedence over all messages. This is based on the principle of Handler's synchronous barrier mechanism and asynchronous message, but these mechanisms are not open to developers, and the relevant methods are also marked @ hide. I guess if they are open to developers, in order to give priority to the execution of their own messages, The messages drawn cannot be executed first, which may lead to Caton. There have been many articles on the principle of synchronous barrier and asynchronous messages, and the analysis of Daniel must be much more thorough than me, ha ha
However, in practice, there will be a demand. Although the drawing of such messages with synchronization barrier mechanism still takes priority, can we give priority to the execution of our own messages in subsequent messages? Refer to the Handler's source code, there are the following methods to achieve this effect
Handler Class: public final boolean postAtFrontOfQueue(@NonNull Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); } public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, 0); } 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); }
2. Other message sending API s, such as post and SendMessage, will eventually call enqueueMessage(). However, the difference lies in the third parameter uptimeMillis in the enqueueMessage() method. sendMessageAtFrontOfQueue calls enqueueMessage method, and the incoming uptimeMillis is 0. The rest are either the current system event or the current system event plus delay time
Handler class public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } // uptimeMillis is the system time or the system time plus the delay time return enqueueMessage(queue, msg, uptimeMillis); }
Look at the difference between sending 0 and not sending 0 in the enqueueMessage method of MessageQueue
MessageQueue class boolean enqueueMessage(Message msg, long when) { //when received the value of uptimeMillis if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. //When when == 0, this logic is followed, which is equivalent to msg as the head node of the new linked list //There is also a condition when < p.when. When will it be triggered? We will analyze it later msg.next = p; mMessages = msg; needWake = mBlocked; } else { //During the execution of else logic, when the time value received is greater than or equal to the system when the normal post and SendMessage are sent The time must be greater than the time of the head node // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }
The analysis shows that if uptimeMillis is 0, the current message will become the header node. Therefore, in addition to the asynchronous message with synchronous barrier, this message must be executed first. Is this true in theory and in practice? The demo is too simple and only the core methods are posted
fun postDemos(){ //1 post handler.post { Log.i(TAG,"1 post") } //2 send message val msg = Message.obtain() msg.what = 1 msg.obj = "2 send message" handler.sendMessage(msg) //3 post handler.post { Log.i(TAG,"3 post") } //4 post handler.post { Log.i(TAG,"4 post") } //5 send message val msg1 = Message.obtain() msg1.what = 1 msg1.obj = "5 send message" handler.sendMessage(msg1) //6 postFirset handler.postAtFrontOfQueue { Log.i(TAG,"6 postFirst") } //7 sendMessageAtTime val msg2 = Message.obtain() msg2.what = 1 msg2.obj = "7 send message" handler.sendMessageAtFrontOfQueue(msg2) }
The log output from this method is as follows
2021-09-19 08:48:45.251 6532-6532/com.hy.learndemo I/MainActivity: 7 send message 2021-09-19 08:48:45.251 6532-6532/com.hy.learndemo I/MainActivity: 6 postFirst 2021-09-19 08:48:45.252 6532-6532/com.hy.learndemo I/MainActivity: 1 post 2021-09-19 08:48:45.252 6532-6532/com.hy.learndemo I/MainActivity: 2 send message 2021-09-19 08:48:45.252 6532-6532/com.hy.learndemo I/MainActivity: 3 post 2021-09-19 08:48:45.252 6532-6532/com.hy.learndemo I/MainActivity: 4 post 2021-09-19 08:48:45.252 6532-6532/com.hy.learndemo I/MainActivity: 5 send message
Obviously, although the messages sent by postatfrontofqueue and sendMessageAtFrontOfQueue enter the queue last, they are executed first, but what's the matter with 7 to 6 executing first? That's because sendMessageAtFrontOfQueue executes later, so the last header node is the message it sends. When analyzing the execution logic of enqueueMessage method, leave a problem. When will trigger when < p.when. In addition to 0, that is, there is a situation where the time value of the message to be sent can be smaller than the time of the header node. Remember the sendMessageAtTime method, which is a public method and is not marked @ hide, so we can also use it. Paste the method again
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Incoming uptimeMillis does not verify that the time is less than the current time, but supports the incoming of messages less than the current time. Therefore, another method to preferentially execute messages is to call the sendMessageAtTime method to pass in a timestamp less than the current time