preface

Handler, Looper, and MessageQueue are all such cliche topics that you might think there is nothing to talk about, and there are a lot of articles online.

I have a few questions to ask you, if you are clear, you don’t need to read this article at all.

1. Can multiple handlers send messages to the same MQ? If so, will multiple handlers process the corresponding message? Why is that?

2. Will Looper exit in an endless loop simply because it can’t get a message?

3. Which is infinite loop, why didn’t the main thread stuck (see this www.zhihu.com/question/34.)

4. A series of questions following question 2. Have you seen the mq.next () method? Ever seen the code for nativePollOnce()? If yes, the call flow is: nativePollOnce() -> pollOnce() -> pollInner(). PollOnce() is an endless loop.

5. What is the relationship between Java layer and Native layer MQ and Looper? What are the priorities?

6. If the main thread is asleep, how does it wake up when you tap the screen? What is the mechanism by which the click event is delivered? (IMS)

Problem resolution

Question 1

Handler has a number of methods for passing events

Such as sendMessage(), post(), and so on

But all of these methods ultimately converge on one method, enqueueMessage()

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // This is the Handler object
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

There are also multiple constructors for this Handler.

 public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
public Handler(Callback callback, boolean async) {
    / / to omit
    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;
}

Copy the code

The Handler initializes either the current thread already has a Looper or binds a Looper to it.

If Looper is not passed and the current thread has not initialized Looper, an exception will be thrown.

The Looper for the main thread is created from looper.prepareMainLooper () in the acitivityThread. main method (when the App process is created).

So, of course, it is possible to create multiple handlers on the main thread bound to the same Looper(MQ corresponds to Looper), and these handler.enqueuemessage () methods send messages to the same MQ corresponding to the same Looper.

But as you can see from enqueueMessage(), the target property of each Message object is set to the Handler that sends the Message(see source code above), so it makes sense that whoever sends the Message will handle it.

A: Multiple handlers can send messages to the same MQ, but whoever sends the Message will be processed because Message retains a reference to the Handler object. (Of course, Looper is also called by message.target to handler.handleMessage () after retrieving Message.)

Question 2

Looper.loop() is known to be an infinite loop, but a closer look at the code reveals some problems

Loop ()

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
    / / to omit
}
Copy the code

(MSG == null) {queue.next(); But that doesn’t happen, and I’ll explain why.

Looper.quit()

Take a look at the quit() method, which calls the mq.quit () method and passes a safe parameter to determine whether to manage the rest of the messages. The specific code is as follows:

void quit(boolean safe) {
    if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr ! = 0 because mQuitting was previously false.nativeWake(mPtr); }}Copy the code

The value mQuitAllowed, if false, is passed in when Looper is initialized.

So, even if we want to call the quit() method to quit the Looper, we need to look at the mQuitAllowed parameter when Looper is created. MainLooper is false, so the quit method cannot be called.

Queue.next () why not null

First look at the code:

Message next(a) {
    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);
    }
    / / to omit
}
Copy the code

MPtr returns null if it is 0, but this is not normally possible because mPtr is a NativeMQ pointer returned by nativeInit when MQ is initialized.

The rest of the code is contained in the for loop and only returns the entity message, whereas nativePollOnce will eventually call epoll_wait(), causing the thread to suspend and hibernate, see question 3.

So unless there’s a system failure, Looper either goes to sleep because it can’t get a message, or it returns a non-empty message.

A: Unless there is a system failure, there is no chance that an infinite loop will exit simply because a message cannot be retrieved. That makes sense. The main Looper quits. What happens to the subsequent events?

Question 3

Check out the zhihu link.

www.zhihu.com/question/34,…).

First, because in the case of epoll_wait(), the main thread is just suspended and does not cost CPU. Second, if an event arrives, the main thread is also woken up by the epoll mechanism. Second, binder thread pools make it possible to wake up the main thread by sending messages from their threads to the main thread via a Handler or other means.

How?

First you need a file descriptor (FD, Linux everything file) and poll support. Epoll_ctl () is then registered, and epoll_wait() waits for the event to arrive.

How does the event come about?

Write data to fd, etc., and wake up according to the message type registered by epoll_ctl.

When Looper handles Java layer enqueueMessage wakes, the old version uses PIPE and the new version uses Eventfd

Question 4 & 5

The messagequeue.next () method Question 2 has already been looked at.

The subsequent native call is nativePollOnce() -> pollOnce() -> pollInner().

The pseudocode looks like this:

// pollOnce
for (;;) {
        while (mResponseIndex < mResponses.size()) {
        const Response& response = mResponses.itemAt(mResponseIndex++);
        int ident = response.request.ident;
        if (ident >= 0) {
            / / to omit
            returnident; }}if(result ! =0) {
        / / to omit
        return result;
    }

    result = pollInner(timeoutMillis);
}

// pollInner
int Looper::pollInner(int timeoutMillis) {
    / / to omit
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        / / to omit
        pushResponse(events, mRequests.valueAt(requestIndex));
    }
    / / to omit
    for (size_t i = 0; i < mResponses.size(a); i++) { Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            / / to omit
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }
            / / to omit}}return result;
}

Copy the code

As you can see, pollOnce() is an infinite loop. However, there are several return conditions, the result value is returned through pollInner(), and it actually calls epoll_wait() to suspend the thread, pollInner().

When an event occurs, we first read the listening event (pushResponse), and then loop through all events. As long as the corresponding event occurs, result will not return a value of 0.

Result has several values

enum {
    /** * Result from Looper_pollOnce() and Looper_pollAll(): * The poll was awoken using wake() before the timeout expired * and no callbacks were executed and no other file descriptors were ready. */
    POLL_WAKE = - 1./** * Result from Looper_pollOnce() and Looper_pollAll(): * One or more callbacks were executed. */
    POLL_CALLBACK = 2 -./** * Result from Looper_pollOnce() and Looper_pollAll(): * The timeout expired. */
    POLL_TIMEOUT = - 3./** * Result from Looper_pollOnce() and Looper_pollAll(): * An error occurred. */
    POLL_ERROR = 4 -};Copy the code

Therefore, the pollOnce() loop doesn’t get stuck, nor does it get stuck in the nativePollOnce() method. When pollOnce() and nativePollOnce() return, the Java layer mq.next () method continues to execute, fetching Message from MQ.java for subsequent calls.

A: pollOnce() does not always jump out of the loop, and will jump out of the loop whenever an event arrives. At the same time, the same set of mechanisms in the Java layer and Native layer can refer to each other, but are completely separate sets of things, including MQ, which is also managed separately. But Native has a higher priority because Java layer events are processed only after Native events are processed.

Question 6

As you can see from InputManagerService, input events are transmitted through sockets. So how does it wake up the main thread?

The Native layer Looper has the addFd() method, and the socket is also a FD. Looper.addfd () registers socket listening with epoll_ctl.

When an input event needs to be passed, the main thread can be woken up by writing data to the socket.

Again, it returns to the code associated with pollInner()

response.request.callback->handleEvent(fd, events, data)

Here is already began to distribute the events related to the main thread, and through the way of Native layer directly invokes the Java code adjustable back, until ViewRootImpl. DeliverInputEvent ()

See: gityuan.com/2016/12/31/…

A: It is also awakened by the epoll mechanism

Worker Pool

In fact, both Android and other event-driven models have similar principles. The following is an introduction to the design of a Worker Pool.

In fact, our main thread can be seen as a consumer, constantly consuming events, while other threads (in Android, InputManagerService, etc.) can be seen as producers, constantly generating event notifications for the main thread to handle, thus making the application run.

www.yangyang.cloud/blog/2018/1…

conclusion

Knowing all of this doesn’t really matter for actual development, and there are still a lot of details left unexplored in the process.

However, it is very necessary to understand the principles and models, so that we can draw inferences from them.

reference

1. www.yangyang.cloud/blog/2018/1…