Android 9.0 native looper mechanism (principle)

Time:2022-5-4

*Standing on the shoulders of giants can see further*

 

Android 9.0 native looper mechanism (principle)

Android 9.0 native looper mechanism (application)

 

preface

When analyzing the native layer code of Android framework, looper, an important auxiliary class in Android system, is used in many places to communicate between threads or design processing logic. This paper will deeply analyze the looper mechanism to understand its operation principle.

  • It’s worth giving a few firstReference articles

  https://blog.csdn.net/xiaosayidao/article/details/73992078

  https://blog.csdn.net/chwan_gogogo/article/details/46953549?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.base&spm=1001.2101.3001.4242

  • In addition, to understand the principle of looper mechanism, additional information is neededUnderstand epool mechanism in Linux

  https://blog.csdn.net/xiajun07061225/article/details/9250579

  • Use of eventfd in Linux

  https://blog.csdn.net/qq_28114615/article/details/97929524

 

Basic class diagram

The message looper mechanism code of Android framework native layer is mainly implemented in:

  system/core/libutils/Looper.cpp

  system/core/include/utils/Looper.h

 

  The basic class diagram is as follows:

 

 

Basic object description

  • Message: the carrier of a message, which represents an event. The event is marked by a what field. The source code is defined as follows:
Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
 * A message that can be posted to a Looper.
 */
struct Message {
    Message() : what(0) { }
    Message(int w) : what(w) { }

    /* The message type. (interpretation is left up to the handler) */
    int what;
};

View Code

  • Messagehandler / weakmessagehandler is the interface (base class) for message processing. Subclasses implement handlemessage to realize the processing logic of specific messages. Weakmessagehandler contains a weak pointer to messagehandler
Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
 * Interface for a Looper message handler.
 *
 * The Looper holds a strong reference to the message handler whenever it has
 * a message to deliver to it.  Make sure to call Looper::removeMessages
 * to remove any pending messages destined for the handler so that the handler
 * can be destroyed.
 */
class MessageHandler : public virtual RefBase {
protected:
    virtual ~MessageHandler();

public:
    /**
     * Handles a message.
     */
    virtual void handleMessage(const Message& message) = 0;
};


/**
 * A simple proxy that holds a weak reference to a message handler.
 */
class WeakMessageHandler : public MessageHandler {
protected:
    virtual ~WeakMessageHandler();

public:
    WeakMessageHandler(const wp& handler);
    virtual void handleMessage(const Message& message);

private:
    wp mHandler;
};

View Code

  • Loopercallback / simplelooppercallback: used for looper callback, which actually saves a looper_ The wrapper base class for the callbackfunc pointer. In looper::addFd() method to set callback when adding monitored FD.
Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
 * A looper callback.
 */
class LooperCallback : public virtual RefBase {
protected:
    virtual ~LooperCallback();

public:
    /**
     * Handles a poll event for the given file descriptor.
     * It is given the file descriptor it is associated with,
     * a bitmask of the poll events that were triggered (typically EVENT_INPUT),
     * and the data pointer that was originally supplied.
     *
     * Implementations should return 1 to continue receiving callbacks, or 0
     * to have this file descriptor and callback unregistered from the looper.
     */
    virtual int handleEvent(int fd, int events, void* data) = 0;
};

/**
 * Wraps a Looper_callbackFunc function pointer.
 */
class SimpleLooperCallback : public LooperCallback {
protected:
    virtual ~SimpleLooperCallback();

public:
    SimpleLooperCallback(Looper_callbackFunc callback);
    virtual int handleEvent(int fd, int events, void* data);

private:
    Looper_callbackFunc mCallback;
};

View Code

  • Looper core class, which maintains a message / monitored FD queue when called by the userWhen pollonce or pollall, it will judge whether there are messages to be processed (call the corresponding handler:: handlemessage) or monitor the occurrence of FD events (callback the corresponding callback function)

Key method analysis

  •  Create looper = = method of creating looper

Looper provides two ways to create a looper:

1. Directly call looper constructor:

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
                        strerror(errno));

    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

View Code

2. Call the static function prepare() of looper: if the thread already has a corresponding looper, it will return directly; otherwise, a new looper will be created.

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

sp Looper::prepare(int opts) {
    bool allowNonCallbacks = opts & PREPARE_ALLOW_NON_CALLBACKS;
    sp looper = Looper::getForThread();
    if (looper == NULL) {
        looper = new Looper(allowNonCallbacks);
        Looper::setForThread(looper);
    }
    if (looper->getAllowNonCallbacks() != allowNonCallbacks) {
        ALOGW("Looper already prepared for this thread with a different value for the "
                "LOOPER_PREPARE_ALLOW_NON_CALLBACKS option.");
    }
    return looper;
}

View Code

Looper’s constructor mainly does two things:
-> call eventfd (0, efd_nonblock) to return mwakeeventfd, which is used to wake up epoll_ wait()
-> call rebuildepolllocked() to create the epoll file descriptor and add mwakeeventfd to the epoll listening queue

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        close(mEpollFd);
    }

    // Allocate the new epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

View Code

Note: the parameter allownoncallbacks indicates whether it can be in looper_ Callback is not provided when addfd

  •  Send message = = method of sending message

Sending a message refers to inserting a message into the message queue mmessageenvelopes. The messages in mmessageenvelopes are stored in chronological order: the smaller the subscript, the earlier it will be processed.
There are three functions to send messages, but they are all implemented by calling sendmessageattime().

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
     * Enqueues a message to be processed by the specified handler.
     *
     * The handler must not be null.
     * This method can be called on any thread.
     */
    void sendMessage(const sp& handler, const Message& message);

    /**
     * Enqueues a message to be processed by the specified handler after all pending messages
     * after the specified delay.
     *
     * The time delay is specified in uptime nanoseconds.
     * The handler must not be null.
     * This method can be called on any thread.
     */
    void sendMessageDelayed(nsecs_t uptimeDelay, const sp& handler,
            const Message& message);

    /**
     * Enqueues a message to be processed by the specified handler after all pending messages
     * at the specified time.
     *
     * The time is specified in uptime nanoseconds.
     * The handler must not be null.
     * This method can be called on any thread.
     */
    void sendMessageAtTime(nsecs_t uptime, const sp& handler,
            const Message& message);

View Code

Take a looksendMessageAtTime()Specific implementation of function:

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

void Looper::sendMessageAtTime(nsecs_t uptime, const sp& handler,
        const Message& message) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ sendMessageAtTime - uptime=%" PRId64 ", handler=%p, what=%d",
            this, uptime, handler.get(), message.what);
#endif

    size_t i = 0;
    { // acquire lock
        AutoMutex _l(mLock);

        size_t messageCount = mMessageEnvelopes.size();
        while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
            i += 1;
        }

        MessageEnvelope messageEnvelope(uptime, handler, message);
        mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

        // Optimization: If the Looper is currently sending a message, then we can skip
        // the call to wake() because the next thing the Looper will do after processing
        // messages is to decide when the next wakeup time should be.  In fact, it does
        // not even matter whether this code is running on the Looper thread.
        if (mSendingMessage) {
            return;
        }
    } // release lock

    // Wake the poll loop only when we enqueue a new message at the head.
    if (i == 0) {
        wake();
    }
}

View Code

First, traverse the mmessageenvelopes according to uptime, find the appropriate location, encapsulate the message into messageenvelope and insert it into the found location.
Then decide whether to wake up looper:

    1. If looper is sending a message at this time, wakeup looper is not required. This time, after the looper processes the message, it will re estimate the next epoll_ The wakeup time of wait().
    2. If it is inserted in the head of the message queue, you need to immediately wake up looper

There are also methods to remove messagesremoveMessages

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
     * Removes all messages for the specified handler from the queue.
     *
     * The handler must not be null.
     * This method can be called on any thread.
     */
    void removeMessages(const sp& handler);

    /**
     * Removes all messages of a particular type for the specified handler from the queue.
     *
     * The handler must not be null.
     * This method can be called on any thread.
     */
    void removeMessages(const sp& handler, int what);

View Code

 

  •  Add FD = = method of adding FD

Add a new file descriptor and set a callback method to monitor events. There are two methods:

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

/**
     * Adds a new file descriptor to be polled by the looper.
     * If the same file descriptor was previously added, it is replaced.
     *
     * "fd" is the file descriptor to be added.
     * "ident" is an identifier for this event, which is returned from pollOnce().
     * The identifier must be >= 0, or POLL_CALLBACK if providing a non-NULL callback.
     * "events" are the poll events to wake up on.  Typically this is EVENT_INPUT.
     * "callback" is the function to call when there is an event on the file descriptor.
     * "data" is a private data pointer to supply to the callback.
     *
     * There are two main uses of this function:
     *
     * (1) If "callback" is non-NULL, then this function will be called when there is
     * data on the file descriptor.  It should execute any events it has pending,
     * appropriately reading from the file descriptor.  The 'ident' is ignored in this case.
     *
     * (2) If "callback" is NULL, the 'ident' will be returned by Looper_pollOnce
     * when its file descriptor has data available, requiring the caller to take
     * care of processing it.
     *
     * Returns 1 if the file descriptor was added, 0 if the arguments were invalid.
     *
     * This method can be called on any thread.
     * This method may block briefly if it needs to wake the poll.
     *
     * The callback may either be specified as a bare function pointer or as a smart
     * pointer callback object.  The smart pointer should be preferred because it is
     * easier to avoid races when the callback is removed from a different thread.
     * See removeFd() for details.
     */
    int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data);
    int addFd(int fd, int ident, int events, const sp& callback, void* data);

View Code

Let’s take a look at the specific implementation:

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

int Looper::addFd(int fd, int ident, int events, const sp& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
            events, callback.get(), data);
#endif

    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }

        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = POLL_CALLBACK;
    }

    { // acquire lock
        AutoMutex _l(mLock);

        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.seq = mNextRequestSeq++;
        request.callback = callback;
        request.data = data;
        if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1

        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                if (errno == ENOENT) {
                    // Tolerate ENOENT because it means that an older file descriptor was
                    // closed before its callback was unregistered and meanwhile a new
                    // file descriptor with the same number has been created and is now
                    // being registered for the first time.  This error may occur naturally
                    // when a callback has the side-effect of closing the file descriptor
                    // before returning and unregistering itself.  Callback sequence number
                    // checks further ensure that the race is benign.
                    //
                    // Unfortunately due to kernel limitations we need to rebuild the epoll
                    // set from scratch because it may contain an old file handle that we are
                    // now unable to remove since its file descriptor is no longer valid.
                    // No such problem would have occurred if we were using the poll system
                    // call instead, but that approach carries others disadvantages.
#if DEBUG_CALLBACKS
                    ALOGD("%p ~ addFd - EPOLL_CTL_MOD failed due to file descriptor "
                            "being recycled, falling back on EPOLL_CTL_ADD: %s",
                            this, strerror(errno));
#endif
                    epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
                    if (epollResult < 0) {
                        ALOGE("Error modifying or adding epoll events for fd %d: %s",
                                fd, strerror(errno));
                        return -1;
                    }
                    scheduleEpollRebuildLocked();
                } else {
                    ALOGE("Error modifying epoll events for fd %d: %s", fd, strerror(errno));
                    return -1;
                }
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}

View Code

Three main things have been done:

1. Put the file descriptor FD, event ID events and callback function pointer to be monitoredcaLlback and some additional parameters data are encapsulated into a request object;

2. Call epoll_ CTL monitors the file descriptor FD;

3. HandleAdd request object to mrequests list (keyedvector) mRequests)

  •  Poll looper = = method of polling processing

You need looper running to process messages. Looper provides an interface: pollonce()
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);

Call poolonce to return. The returned results are as follows:

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

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, // wake up through wake() before timeout, no callback is executed, and there is no file descriptor event

    /**
     * Result from Looper_pollOnce() and Looper_pollAll():
     * One or more callbacks were executed.
     */
    POLL_ Callback = - 2, // one or more callbacks are executed

    /**
     * Result from Looper_pollOnce() and Looper_pollAll():
     * The timeout expired.
     */
    POLL_ Timeout = - 3, // timeout

    /**
     * Result from Looper_pollOnce() and Looper_pollAll():
     * An error occurred.
     */
    POLL_ Error = - 4, // an error occurred
};

View Code

If there is no event / message to process in the looper, it will be blocked in epoll_ Wait() waits for the event to arrive.
Calling process: pollonce() – > pollinner() – > epoll_ wait()

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

View Code

epoll_ Wait() will return in three cases. The return value eventcount is the number of epoll events.
-An error is returned, eventcount < 0;
-Timeout returns, eventcount = 0, indicating that no event occurs in the monitored file descriptor, and the native message will be processed directly;
-If there is a return caused by an event in the monitored file descriptor, eventcount > 0; Epoll events with the number of eventcount.

epoll_ After the wait wakes up, the next step is to judge whether there is a monitored file descriptor event, and encapsulate the event ID events and the corresponding as a response object and add it to the mresponses queue (vector mResponses)

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

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);
            }
        }
    }

View Code

Then start to traverse the message queue, judge whether there is a message that has reached the processing time, and call the handler – > handlemessage (message) corresponding to the message;

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

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 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;
        }
    }

View Code

It will also traverse the mresponses queue and call back the corresponding response request. callback->handleEvent(fd, events, data)

Android 9.0 native looper mechanism (principle)Android 9.0 native looper mechanism (principle)

// 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.
            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;
        }
    }

View Code

Note: after each message is processed, it will be removed from the message queuemMessageEnvelopes.removeAt(0);

After each FD event callback is completed, it will also be retrieved from theMrequests listRemove from

For pollinner ()

Adjust the timeout based on when the next message is due
Mnextmessageuptime is the time point of the latest message to be processed in the message queue mmessageenvelopes.
Therefore, it is necessary to calculate a minimum timeout according to the comparison between mnextmessageuptime and the timeout millis passed down by the caller, which will determine epoll_ How long will wait () return after it may block.

    epoll_wait()
Handle epoll_ Epoll events returned by wait()
Judge which FD epoll event occurs
If it is mwakeeventfd, execute wake() Wake () just reads out the data and continues processing. Its purpose is to make epoll_ Wait() returns from blocking.
If it is through looper The FD added to the epoll listening queue by the addfd () interface is not processed immediately, but is pushed to mresponses first and then processed later.
Process the message in the message queue mmessageenvelopes
If the processing time is not up yet, update mnextmessageuptime
Handle the event just put into mresponses
Only handle ident as poll_ Callback event. Other events are handled in pollonce

Basic operation mechanism

Probably draw an abstract processing concept diagram, which is not necessarily accurate

  • Create a message, specify a messagehandler, and call sendmessage() to pass the message to looper.
  • Looper creates messageenvelope based on message and messagehandler. Then add messageenvelope to looper’s message queue mmessageenvelopes.
  • In addition to processing messages, native looper can also listen for specified file descriptors.
  • Add FD to be listened to into epoll’s listening queue through addfd(), encapsulate the incoming FD, ident, callback and data into a request object, and then add it to looper’s mrequests.
  • External logic (which can be a thread) constantly calls poolonce – > pollinner() – > epoll_ Wait() blocks, waiting for an event to occur or timeout
  • Epoll when the FD event occurs_ Wait() will return epoll event, then find the corresponding request object from mrequests, and add the returned epoll event type (epolin, epollout…) to encapsulate it into a response object and add it to mresponses.
  • Then, when the response needs to be processed, take the response from the mresponses traversal for processing.
  • Similarly, traverse the messages in the message queue mmessageenvelopes for processing
  • So continuous cycle

 

 

 

 

Recommended Today

RPM management package for Linux

#Install package rpm -ivh zsh-5.5.1-6.el8.x86_64.rpm #Check whether the installation is successful rpm -q zsh #View the list of installed files rpm -ql zsh #Uninstall package rpm -e zsh #Ignore dependencies uninstall package rpm -e zsh –nodeps #Check whether the uninstallation is successful rpm -q zsh #Upgrade or install the software package. If it is not […]