This is the 5th day of my participation in the August More Text Challenge
The familiar and unfamiliar handler-3
To:
The familiar and unfamiliar handler-1
The familiar and unfamiliar handler-2
NativeInit:
The first JNI call covered in the Handler three-piece creation process above is the nativeInit method of MessageQueue.
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit(a); }Copy the code
Concrete implementation in the framework/base/core/jni android_os_MessageQueue. CPP in:
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
// Create the NativeMessageQueue object that holds the Looper object on the Native side
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(a);// If the creation fails, an exception occurs
if(! nativeMessageQueue) {jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
// Increase the reference count for NativeMessageQueue
nativeMessageQueue->incStrong(env);
// Return nativeMessageQueue to the Java layer
return reinterpret_cast<jlong>(nativeMessageQueue);
}
Copy the code
In the constructor of NativeMessageQueue:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
// Get the Native Looper object of the current Thread through TLS
mLooper = Looper::getForThread(a);if (mLooper == NULL) {
// If not, a Native Looper object will be created
mLooper = new Looper(false);
// Save the created Looper object to TLS
Looper::setForThread(mLooper); }}Copy the code
Note that the Looper object here is not directly related to the Java layer Looper object. In its construction, two things are most relevant:
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
// Initialize the file descriptor mWakeEventFd for the wake event
// eventfd This system call is used to create or open an eventfd file, similar to the open operation of the file
// The initial value passed in here is 0, and the flag bit set is
// EFD_CLOEXEC: FD_CLOEXEC; FD_CLOEXEC: FD_CLOEXEC; FD_CLOEXEC: FD_CLOEXEC; FD_CLOEXEC;
/ / EFD_NONBLOCK:
// The file is set to O_NONBLOCK (non-blocking IO, return -1 if no data is read or if the write buffer is full),
// Instead of blocking the call, generally set.
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
AutoMutex _l(mLock);
// Recreate the Epoll event of the current Looper
rebuildEpollLocked(a); }Copy the code
RebuildEpollLocked implements the following:
void Looper::rebuildEpollLocked(a) {
// If Looper already has an EpollFd, i.e., an old epoll instance, reset it
if (mEpollFd >= 0) {
mEpollFd.reset(a); }// Create a new Epoll instance
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
struct epoll_event eventItem;
// Initializes the eventItem memory space
memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
// EPOLLIN: indicates that the corresponding file descriptor is readable
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd.get(a);// Call epoll_ctl on the Epoll instance corresponding to mEpollFd and call mWakeEventFd
// Add to the epoll instance corresponding to mEpoll
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
for (size_t i = 0; i < mRequests.size(a); i++) {const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
// Add Request to the epoll instance as well
int epollResult = epoll_ctl(mEpollFd.get(), 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)); }}}Copy the code
The epoll_event structure is as follows:
struct epoll_event {
uint32_t events;
epoll_data_t data;
}
typedef union epoll_data {
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
Copy the code
The Events member variable can be a collection of the following macros:
- EPOLLIN: indicates that the corresponding file descriptor can be read (including that the peer SOCKET is normally closed).
- EPOLLOUT: indicates that the corresponding file descriptor can be written.
- EPOLLPRI: indicates that the corresponding file descriptor has urgent data to read (it should indicate that out-of-band data arrives).
- EPOLLERR: indicates that an error occurs in the corresponding file descriptor.
- EPOLLHUP: the corresponding file descriptor is hung up.
- EPOLLET: Set EPOLL to Edge Triggered mode, as opposed to Level Triggered.
- EPOLLONESHOT: monitors only one event. If you want to continue monitoring the socket after this event, you need to add the socket to the EPOLL queue again.
Since the MessageQueue of Java layer is created when the MessageQueue of Native layer is created, the destruction of MQ of Java layer will also trigger the destruction of NativeMessageQueue. The destruction of MQ of Native layer is relatively simple. This is essentially the elimination of an object in Native layer:
- Removes a reference to an object.
- The delete call clears the object’s memory space.
NativeWake:
From the above analysis, we know, when we call the Java layer MessageQueue. EnqueueMessage, felt the need to wake up in the Java layer when the message queue, would call nativeWake the native method:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
// Call NativeMessageQueue's wake method.
nativeMessageQueue->wake(a); }Copy the code
NativeMessageQueue’s wake method is the Native Looper’s wake method:
void Looper::wake(a) {
uint64_t inc = 1;
// Write the character 1 to the pipe mWakeEventFd
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
if(nWrite ! =sizeof(uint64_t)) {
if(errno ! = EAGAIN) {LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
mWakeEventFd.get(), nWrite, strerror(errno)); }}}Copy the code
TEMP_FAILURE_RETRY Specifies the macro expression that evaluates the incoming expression to -1. If the expression returns -1 and the error code is EINITR (4), it will retry until it succeeds.
NativePollOnce:
From the above analysis, we know that before Java layer Message queue processes Message, nativePollOnce will be called to process the Message of Native layer:
nativePollOnce(ptr, nextPollTimeoutMillis);
Copy the code
The PTR is a “pointer” to the MessageQueue created earlier in the Native layer, and nextPollTimeoutMillis indicates when the next message will be fetched.
In android_os_MessageQueue. CPP, nativePollOnce is implemented as follows:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, Jint timeoutMillis) {// Convert the mPtr passed by the Java layer to NativeMessageQueue* NativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); NativeMessageQueue ->pollOnce(env, obj, timeoutMillis); }Copy the code
PollOnce:
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
// ...
// Call Looper's pollOnce method
mLooper->pollOnce(timeoutMillis);
// ...
}
Copy the code
Stars pollOnce eventually in the system/core/libutils/stars. The CPP:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// First process Native layer Response
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++)
// Ident >=0 indicates no callback
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if(outFd ! =nullptr) *outFd = fd;
if(outEvents ! =nullptr) *outEvents = events;
if(outData ! =nullptr) *outData = data;
returnident; }}// If there is result, then exit
if(result ! =0) {
if(outFd ! =nullptr) *outFd = 0;
if(outEvents ! =nullptr) *outEvents = 0;
if(outData ! =nullptr) *outData = nullptr;
return result;
}
/ / call pollInner
result = pollInner(timeoutMillis); }}Copy the code
The structure of Response and Request is as follows:
struct Request {
// Request associated file descriptor
int fd;
// requestId: POLL_CALLBACK (-2) indicates a callback
int ident;
int events;
int seq;
// Request processing callback
sp<LooperCallback> callback;
void* data;
void initEventItem(struct epoll_event* eventItem) const;
};
struct Response {
int events;
Request request;
};
Copy the code
The implementation of Looper::pollInner is as follows: internally, the blocking method epoll_wait is called to obtain the number of Epoll events occurring, and then, according to this number,
int Looper::pollInner(int timeoutMillis) {
// 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; }}// Poll.
int result = POLL_WAKE;
As is known from the above analysis, mResponses have been processed before pollInner is called
mResponses.clear(a); mResponseIndex =0;
Epoll is about to start.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// Wait for the epoll_wait system call to return, which returns the number of epoll events that occurred on the file descriptor mEpollFd within timeoutMillis
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// The polling ends, and the events received begin processing.
mPolling = false;
// Acquire lock.
mLock.lock(a);For example, if there is an exception, the Epoll mechanism needs to be re-created
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked(a);goto Done;
}
// If the number of epoll events is less than 0, an exception is considered and the command is switched to Done to continue
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
result = POLL_ERROR;
goto Done;
}
// If the value of epoll event is 0, polling times out
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
// Start looping over all events
for (int i = 0; i < eventCount; i++) {
// Get the FD of an event
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
// If it is a wake up event
if (fd == mWakeEventFd.get()) {
if (epollEvents & EPOLLIN) {
// It has been woken up to read and empty the pipe
awoken(a); }else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); }}else {
// Use the file descriptor to find the corresponding Request index
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;
// Encapsulate the corresponding request as a Response object and push it into mRequests and Vector
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
// The Native Message event is processed.
mNextMessageUptime = LLONG_MAX;
// mMessageEnvelopes is a Vector, MessageEnvelopes is the message envelope as its name indicates
// Encapsulates Message and MessageHandler objects
MessageHandler defines a handleMessage method
// A Native Message can be sent by calling Looper::sendMessageXX
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
// Remove and execute a message when it is ready to be processed
// Corresponding to the handleMessage method of MessageHandler.
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock(a); handler->handleMessage(message);
} // release handler
mLock.lock(a); mSendingMessage =false;
result = POLL_CALLBACK;
} else {
// If the message at the head of the queue has not reached the time needed to be processed
// The queue needs to hang until the header message can be processed
mNextMessageUptime = messageEnvelope.uptime;
break; }}// Release lock.
mLock.unlock(a);// Then deal with mResponses (Request) pushed in above
for (size_t i = 0; i < mResponses.size(a); i++) { Response& response = mResponses.editItemAt(i);
// 有callback
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
/ / callback execution
int callbackResult = response.request.callback->handleEvent(fd, events, data)
if (callbackResult == 0) {
removeFd(fd, response.request.seq);
}
// Clear the reference to callback
response.request.callback.clear();
result = POLL_CALLBACK;
}
}
return result;
}
Copy the code
The key point of the whole Native layer Looper mechanism is the epoll_wait system call in Looper::pollInner, which blocks the current thread and releases system resources when there is no work to be done in the message queue, that is, Looper’s dead-loop mechanism does not hog system resources all the time. The main thread is blocked when there are no tasks to be processed, so it does not hog resources too much.
Or, more generally, looper. loop, which we see, starts an infinite loop, which is exactly what it is. But what’s special about Looper. Loop is that instead of writing an infinite loop, where the CPU keeps executing and then explodes, Looper. Loop blocks and stops after epoll_wait when there are no messages to process.
We don’t feel that the main thread stops because we write a code that executes passively. We post a message on the child thread, the MessageQueue receives the message, and the Looper. Loop on the main thread executes the code and fetches the next message after executing the code. The main thread continues to block. The code we wrote executed, of course, without feeling that the main thread was blocked.
Summary:
Through the analysis of Native pollOnce, we know that Android’s message processing mechanism actually spans the Java layer and Native layer.
Java layer and Native layer, through some JNI calls above and the key pointer mPtr, the Java layer MessageQueue and Native MessageQueue are associated, so that when the message mechanism of Java layer is running, The Native layer’s messaging mechanism works as well.
The process of Message processing is to process Native messages first, then Native requests, and finally Java layer messages after pollOnce. Therefore, sometimes there are not many messages in the Java layer but the slow response time may be caused by the message mechanism of the Native layer.