Improve the priority of sending messages by Handler in Android practice

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

Keywords: Android kotlin

Added by bsbotto on Tue, 21 Sep 2021 05:22:33 +0300