Looper is actually very simple

Keywords: Android

Every time I mention this looper, I feel a little despised. It's just a news queue. But when others ask, I don't know where to start. This time, I stroked the Tao. After stroking it clearly, I found that it was still very simple, ha ha.

Looper is a way of continuously processing messages in a single thread.

Several important classes are involved
Looper / / scheduling of messages
MessageQueue / / message storage
Message / / the message itself can load data. It can also be divided into synchronous messages and asynchronous messages
Handler //callback, runable, message entry and processing place

Take the Looper of the main thread as an example

Create Looper

ActivityThread.java

public static void main(String[] args)  {
    
    Looper.prepareMainLooper();
    ...
    Looper.loop();
}

Looper.java

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        //Only one call is allowed
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //
        sMainLooper = myLooper();
    }
}


private static void prepare(boolean quitAllowed) {
    //Each thread can only be created once. If called, each thread has its own looper
    //For this reason, a class HandlerThread is derived, which is a thread that internally starts Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

A knowledge point ThreadLocal needs to be inserted here

End Looper

Because the operation is more important, we put the end before. There are two methods, quit and quitSafe. The difference is that quit will queue all the messages in the queue, while quitSafe only processes the messages in the messages that are greater than the current time
Looper.java

public void quit(){}
public void quitSafe(){}

Run Looper, message processing

Looper.java

//Call this to start the loop. emm.. the code is so simple after simplification.
//The overall idea is to constantly get messages from the queue, throw them to the target of the message for distribution, and block them if there is no message
looper.loop() {
    ...
     for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
               //Find the handler corresponding to msg to process the message
                msg.target.dispatchMessage(msg);
            } finally {
               
            }
    }
    ...
}

MessageQueue.java fetching data

Let's Kangkang queue.next What's in there

Message next() {

    for (;;) {
        
        //Here, blocking will be carried out and event messages will be processed. Epoll is inside_ wait
        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;
                //msg.target == null is the barrier. Only asynchronous messages are retrieved after the barrier is encountered
                //There is a problem here. If a barrier is thrown in, the ANR will be stuck if it is not cancelled
                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) {
                    //The messages in the whole queue are actually sorted by timestamp. If the time is not up
                    //The execution time will continue to wait. The delay is the delay message, such as handler.postDelay, which is usually used
                    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);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        //If you take a message from the middle of the queue, you need to re link the linked list
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                        //If it is taken from the original node, it is good to assign a value directly
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //Here we go straight back
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            //When there is no message processing for an idle process, the logic is triggered again
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
    }
    
}

The above process completes the looper on the java side.
If you don't want to understand the IO distribution process, you can completely ignore the details in native pollonce (PTR, next polltimeoutmillis)

It can be understood that one thing has been done inside

//wait method using epoll
  int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

Throw data in queue

MessageQueue.java

 boolean enqueueMessage(Message msg, long when) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //Insert a data in front. If it is smaller than the current time
            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.
                //If it is currently blocked, and the first message is a barrier message, and the inserted message is an asynchronous message, the asynchronous message is not responsible for waking up the blocking
                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);
            }
 }

Wake up action

Looper.cpp

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    //Focus on this sentence, write a value to the wake-up file descriptor of epoll
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
                    mWakeEventFd, strerror(errno));
        }
    }
}

To sum up, the whole process is

  • Suspend wake-up: a wakeFd file is monitored through epoll. If there is no message, epoll can be called_ Wait suspend thread,
    When there is a message, write a value of 1 to wakeFd to wake up epoll_wait blocking.
  • Message: messages are divided into asynchronous and synchronous. A target(Handler) is bound to process messages,
    If the target is null, it is a barrier message. The function of the barrier message is that all backward synchronization messages are behind the barrier message unless the barrier is cancelled.
    It is generally used in viewRootImpl performTravels triggered by invalidate to ensure the priority of UI refresh.
    An example of using asynchronous messages is: it is useful in Choreographer's postCallbackDelayedInternal.
    In addition, the concept of reuse is used. There is a message pool,
    After all, a message is only a carrier, and it is not necessary to generate objects repeatedly every time. It is recommended to use Message.obtain() to create messages
  • Message queue: queued messages are sorted according to the time stamp of the message. If a non asynchronous message is inserted and has been blocked, the wake-up action is performed
  • Handler: if you don't see this, it's because it's actually unimportant. The target of message points to a handler. Then it will call the handler's disptachMessage. Here also introduces a knowledge point, the disclosure of handler. Suppose that the scenario Activity creates an instance of anonymous internal class handler to process messages, Then the reference relationship is messagequeue - > message - > handler - > Activity. Well, it will be disclosed

OK, the above has the value of the whole queue message, including the actions of queue entry, blocking and wake-up.
Of course, the big brother of toutie said that I need to understand the details of the nativePollOnce you said to skip. In fact, it is not complicated. epoll can monitor multiple files. In addition to monitoring a file for the upper layer to wake up, it can also monitor additional files. What is useful in practice is the writing of socket files distributed by monitored events

android_os_MessageQueue.cpp

//There is a dynamically registered mapping between java methods and native methods

static const JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
    { "nativeSetFileDescriptorEvents", "(JII)V",
            (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};

//The corresponding method is android_os_MessageQueue_nativePollOnce
//It also introduces a class NativeMessageQueue
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

Looper.h (core/libutils/include/utils/)

 inline int pollOnce(int timeoutMillis) {
        return pollOnce(timeoutMillis, NULL, NULL, NULL);
    }

Looper.cpp

//For the pollOnce in the upper layer, the following are NULL except timeoutMillis
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
    //Each request is actually an encapsulated fd listener
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}



int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    //Here's the point. epoll_wait, blocking and supporting timeout
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif
   //Read event
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message);
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // Invoke the callback.  Note that the file descriptor may be closed by
            // the callback (and potentially even reused) before the function returns so
            // we need to be a little careful when removing the file descriptor afterwards.
            
            //The key point is to call and process the callback. This callback is like the handler of the java layer, which handles the specific logic under the callback
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }

            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}


Related knowledge points: reuse of ThreadLocal Epoll memory leak message objects

Posted by e11even on Fri, 05 Nov 2021 18:24:19 -0700