Handler source code analysis of three native layers

The first two chapters explained the basic use of Handler and the functions of asynchronous messages. In this chapter, we have an in-depth understanding of the native layer of Handler. We learned in the first chapter that the construction of Handler requires the creation of Looper, and the creation of Looper requires the creation of MessageQueue. Let’s directly look at the construction of MessageQueue, and put in the variables related to native:

1 constructor

package android.os; public final class MessageQueue { private long mPtr; // This mPtr is a private Native static long nativeInit() returned by native layer; private native static void nativeDestroy(long ptr); private native void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr); private native static boolean nativeIsPolling(long ptr); private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); Android_os_MessageQueue_nativeInit = android_os_MessageQueue_nativeInitCopy the code

Let’s take a look at the corresponding native functions:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, Jclass clazz) {// Create native layer MessageQueue NativeMessageQueue* NativeMessageQueue = new NativeMessageQueue(); / /... NativeMessageQueue ->incStrong(env); // Convert the native layer's MessageQueue pointer to a long and return it to the Java layer, i.e. MPtr return reinterpret_cast< jLong >(nativeMessageQueue); } / / NativeMessateQueue constructor NativeMessageQueue: : NativeMessageQueue () : mPollEnv (NULL), mPollObj (NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); // Get Looper from current Thread if (mLooper == NULL) {mLooper = new Looper(false); // Create Looper::setForThread(mLooper); // Put the current thread}}Copy the code

Next we look at the constructor for the Native layer Looper

Looper::Looper(bool allowNonCallbacks): mAllowNonCallbacks(allowNonCallbacks), //... Set up the file descriptor mWakeEventFd. Reset (eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC)); / /... RebuildEpollLocked (); }Copy the code

Let’s take a look at Linux’s epoll mechanism:

1 on fd

On Linux, everything exists in the form of files. Devices, drivers, etc., are files, such as binder drivers. These files are described by the file descriptor FD, which is a non-negative integer representing the number of a file. Fd is the index of this table, where each entry has a pointer to an open file. So we can think of FD as file, driver, device, etc.

2 Correlation methods of epoll

    1. Epoll_create: Creates an epoll object
    1. Epoll_ctl (epollfd, option, fd, event): This method can be divided into many functions based on parameters

Options can be of the following types: EPOLL_CTL_ADD Add EPOLL_CTL_DEL Delete EPOLL_CTL_MOD modify

The event can be of the following types: EPOLLIN File descriptor readable EPOLLOUT File descriptor writable EPOLLERR File descriptor error EPOLLHUP File descriptor hangs

    1. epoll_wait(epollfd,…) : Wait until something corresponding to ePollfd occurs

3 About the epoll mechanism

EPOLLIN means readable, and EPOLLOUT means writable. After the writing end writes data, the reading end immediately senses it and reads data. After the reading end reads data, the writing end immediately senses it and writes data

Now let’s look at the epoll code:

Void stars: : rebuildEpollLocked () {/ / if the file descriptor has shut down if there's a (mEpollFd > = 0) {mEpollFd. Reset (); } // Create a new epoll file descriptor and add it to the wake pipeline mepollfd.reset (epoll_create1(EPOLL_CLOEXEC)); struct epoll_event eventItem; // Create an epoll event eventItem memset(& eventItem, 0, sizeof(epoll_event)); Eventitem. events = EPOLLIN; // Set the event type to EPOLLIN, which is the readable event eventitem.data.fd = mwakeEventfd.get (); Epoll int result = epoll_ctl(mepollfd.get (), EPOLL_CTL_ADD, mwakeEventFd.get (), &eventItem); 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.get(), EPOLL_CTL_ADD, request.fd, &eventItem); }}Copy the code

Here we summarize:

  • 1 we need to create Handler;
  • 2. Create a Looper, so we create a Looper.
  • 3. Since creating Looper requires creating MessageQueue, we create MessageQueue;
  • 4. When creating MessageQueue, we find that it calls nativeInit();
  • 5 Then we enter the native layer of nativeInit();This is starting to go native
  • 6. We found that a native MessageQueue was created in nativeInit
  • 7 Then we follow into the construction of MessageQueue and discover the creation of native Looper
  • 8 Then we enter Looper’s construction discovery1 Create a file descriptor. 2 Create epoll
  • 9 Then we went into the code that created epoll and found that a wake up event was registered
  • 10 Logical End

Let’s see what happens after we post a message.

2 message

We go through chapter 1 until we send the message and then we go to MessageQueue and we look at enQueue

boolean enqueueMessage(Message msg, long when) { synchronized (this) { //... boolean needWake; if (p == null || when == 0 || when < p.when) { //... needWake = mBlocked; } else {// Whether needWake = mBlocked && p.target == null && msg.isasynchronous (); / /... } if (needWake) {nativeWake(mPtr); if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Let’s take a look at nativeWake() of the native layer:

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, Jlong PTR) {// Call MessageQueue NativeMessageQueue* NativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); NativeMessageQueue ->wake(); } void NativeMessageQueue::wake() {} void NativeMessageQueue::wake() { } void Looper::wake() { uint64_t inc = 1; // The file descriptor mWakeEventFd is written to inc. The epoll mechanism tells us that when data is written to inc, the reader immediately senses that the data has been written to inc. Ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(Uint64_t))); / /... }Copy the code

To sum up:

  • 1 We send a message in the Java layer
  • 2 will eventually be called to the Enqueue in the Java layer’s MessageQueue
  • NativeWake () is called if it needs to be woken up.This is going to be native
  • 4. NativeWake eventually calls native Looper’s Wake ().
  • 5 The wake() function finally writes a wake event, notifying that a message has been written
  • 6 So how does the Java layer receive this message

3 to receive the message

After chapter 1 we know that the message is processed in looper.loop () and retrieved from messagequeue.next ().

Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } int nextPollTimeoutMillis = 0; for (; ;) {/ /... NextPollTimeoutMillis nativePollOnce(PTR, nextPollTimeoutMillis); / /... }}Copy the code

NativePollOnce (PTR,time) is called at the beginning of the code simplification. Let’s look at this function:

Static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong PTR, jint timeoutMillis) { Interpret_cast <NativeMessageQueue* NativeMessageQueue = Reinterpret_cast <NativeMessageQueue*>(PTR) PollOnce () nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { //... PollOnce (Looper) {pollOnce (Looper) {pollOnce (Looper) {pollOnce (Looper) {pollOnce (Looper) {pollOnce (Looper); mLooper->pollOnce(timeoutMillis); / /... } int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; // Infinite loop... for (;;) {// The condition for the end of the loop is result! If (result!) = 0, that means if (result! = 0) {/ /... return result; } // if it's 0, it keeps running, so, pollInner actually doesn't return to 0, that is, as long as it returns, it closes the cycle result = pollInner(timeoutMillis); }}Copy the code

The core pollInner method, whose argument is the nextPollTimeoutMillis argument passed by the Java layer:

int Looper::pollInner(int timeoutMillis) { //... POLL_WAKE = -1 POLL_CALLBACK = -2 POLL_TIMEOUT = -3 POLL_ERROR = -4 int result = POLL_WAKE; EPOLL_MAX_EVENTS=16 struct epoll_event eventItems[EPOLL_MAX_EVENTS]; // Call epoll_wait() to wait for an event. If there is an event, put it into eventItems and return the number of events. If there is no event, wait. TimeoutMillis int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); The lock (); / / locking mLock. Done if (eventCount <= 0) {if (errno == EINTR) {goto Done; } result = POLL_ERROR; goto Done; } // eventItems for (int I = 0; i < eventCount; Int fd = eventItems[I].data.fd; Uint32_t epollEvents = eventItems[I]. Events; If (fd == mWakeEventFd) {// If the file descriptor is mWakeEventFd if (epollEvents & EPOLLIN) {// If the event type is EPOLLIN (readable event) // There is data to read, Awoken () is called to read the data until it is finished. } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } } else { //... If (eventCount<=0) {eventCount<=0; mNextMessageUptime = LLONG_MAX; //mMessageEnvelopes is a Vector of native layer messages while (mMessageEnvelopes. Size ()! = 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); Envelop: Envelop: Envelop: Envelop: Envelop: Envelop: Envelop: Envelop: Envelop: Envelop Jave layer Message const MessageEnvelope& MessageEnvelope = mMessageEnvelopes. ItemAt (0); // Determine the execution time of the message, If (messageEnvelope. Uptime <= now) {// Fetch Handler sp<MessageHandler>  handler = messageEnvelope.handler; Message = messageEnvelope. Message; mMessageEnvelopes.removeAt(0); // Delete, because mSendingMessage = true is then processed; // Unlock mlock. unlock(); Handler ->handleMessage(message); } mLock.lock(); mSendingMessage = false; result = POLL_CALLBACK; } else {mNextMessageUptime = messageEnvelope. Uptime; NextPollTimeoutMillis break; nextPollTimeoutMillis break; }} // unlock mlock. unlock(); / /... return result; }Copy the code

Ok, summary:

  • 1 We need to process the message, so we fetch the message first, so we enter the Java layer MessageQueue
  • 2 if the current message is not executed, how to do, calculate how long to execute, get timeout, then call nativePollOnce(timeout);
  • 3 Then we enter the native MessageQueueEnter native
  • 4. We tracked down all the way, and found that Native MessageQueue was just a mouth eater without any dry hair, and finally entered native Looper
  • 5. We find that Looper’s pollOnce() is a dead loop, internal called pollInner(). As long as pollInner() returns, it breaks out of the loop and returns to the Java layer for further execution
  • 6 enters pollInner(), we find that there are three logical waiting events, and the timeout time is the timeout transmitted from the Java layer. If an event occurs, it will return the event that occurs and process the event that does occur, then it takes out the event and processes the event that does not occur. Return result for processing native messages, and result! = 0
  • 7 Return to the Jave layer and continue processing

Here are a few questions, let’s go through them first:

  • NativePollOnce () is called if the Java layer Message should not be processed. Where in the code to wait?

A: In native Looper’s pollOnce, epoll_wait(timeout), as long as the time is not timeout, will wait, equivalent to stuck here, do not proceed down

  • 2 If I pass timeout -1, I wait forever, then I’m stuck here

A: If an event occurs during the waiting period, the number of events will be returned immediately. At this time, the number of events will be read and processed by executing down to find the event. For example, when the Java layer enqueue event occurs, it will run to nativeWake() and write a wake event. Epoll_wait () then listens for the wake event and immediately returns, executing down.

  • 3 What if there is no event processing?

A: Then wait for timeout and return 0 directly. At this time, the native message will be executed. The goto Done in the code is this logic

Conclusion: The Java layer realizes waiting through nativePollOnce(timeout). The method to wake up is either timeout after the timeout time, or write an event through nativeWake(), so that native can listen to the event and return. Eg, this example must be seen with code best:

1 Now call handler.sendMessageDelay(MSG,3000); Messagequeue.next () : MSG. When > now NextPollTimeoutMillis = 3000, mBlockd = true, and nativePollOnce(nextPollTimeoutMillis) is called to wait. Result is initialized to 0, so it is always in for, pollOnce(), and pollOnce() waits for 3 in epoll_wait(), at which point we call handler.sendMessage(MSG), Let a message executed immediately 4 at this point in the MessageQueue. EnqueueMessage (), insert this information into the first delay 3000 ms in front of the message, and then find the when = = 0, so he will needwake = mBlocked, True, and then nativeWake() is called; Looper's epoll_wait() responds immediately to the wake() event, and pollOnce() returns result because result! =0, return to Java layer, so MessageQueue next immediately active, immediately take out the just sendMessage(MSG) to start processing, after processing to the next (i.e. 3000ms delay), find MSG. If there is no new message, repeat the preceding procedure. If there is no new message, wait for epoll_wait() to return with a timeout of 3000ms.Copy the code

The last

Handler belongs to the path of Android advanced development, in-depth understanding of its design principle is of great benefit: Handler call process Handler synchronization barrier and asynchronous message mechanism Handler Native layer implementation