How handlers implement delayed messages is an old question. Here I will take you from the point of view of the source code, and the handler all aspects of the implementation of the leak to fill the gap. The handler core method for sending messages is sendMessage. Post is essentially a syntax sugar for the handler, passing runnable to help us build a message.
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
You can see that getPostMessage helps us build a message and then call sendMessageDelayed.
So sendMessage, just like startActivity ends up at startActivityForResult, handler all the methods that send messages end up at sendMessageDelayed, It’s just the delayMillis, which is the delayed time.
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code
This will then add DelayMillis to the current boot time, which will then go to the sendMessageAtTime method
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
At this point, the handler is done sending the message to the messageQueue. Each message takes an uptimeMillis parameter, which is how long it will be delayed.
Let’s look at queue.enqueueMessage in messageQueue.
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 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;
}
Copy the code
The target value sent by the handler must not be empty, in order to determine which handler sent the message from the target value.
By the way, not all MSG and target values need to be non-empty (handler’s synchronization barrier is a target-empty MSG that is used to execute asynchronous methods first). An important place to use synchronization barriers is to receive Vsync signals to refresh the page view. In order to ensure the smoothness of the view, every time the refresh signal comes, you should put other tasks on the back burner and refresh the page first.
The next step is to insert the MSG into the queue in order of the actual execution time (see the for loop).
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false; }}Copy the code
Ok, so now that the queue is built, let’s say I’m going to delay my first message by 10 seconds. Actually go one way.
Let’s say I’m looper now, and I’m going to iterate over this messageQueue, I’m definitely going to call the next method.
The next() method is longer, and I’ll stick to the core of the delayed message.
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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
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); }........................... The following is omittedCopy the code
You can see that this is also a for loop iterating through the queue, and the core variable is nextPollTimeoutMillis. As you can see, after nextPollTimeoutMillis is calculated, the native method nativiePollOnce is called. NextPollTimeoutMillis is the sleep time passed in, similar to Java’s sleep(time). Hibernate until the next message. What if I insert a new message in between, so handler wakes up every time I insert a message, recalculates the insert, and walks through the dormant process again.
NativiePollOnce, a native method known by its name, uses the Linux epoll mechanism to call the epoll_wait method.
int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
Copy the code
Epoll is an I/O multiplexing mechanism for Linux, as well as select. The main principle is not in-depth, but here is a general understanding of the I/O multiplexing mechanism and its difference from SELECT.
I/O multiplexing in Linux: For example, when we are fishing, to ensure that we can catch the most fish in the shortest time, we place several fishing rods and fish at the same time. Then which rod has a fish bite, we will be the fishing rod on the fish up. So what we’re doing here is we’re putting all these messages into this mechanism, and when the time is up, we execute that message.
Epoll and SELECT Epoll obtains events in a space-for-time manner, which is similar to event-driven. If there is an event to be executed, epoll will notify Epoll. Therefore, the time complexity of epoll is O (1).
For reference only, welcome correction