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