Handler, which we Android engineers use every day, is mainly used for communication between threads. We are familiar with this part of the knowledge, but recently I looked through the source code of Handler, found that I have a new understanding of the Handler. So to summarize this article for the record, the source code of this article is based on 8.0.

Before the article begins, I will assume that you have the basic knowledge of Handler, Looper, MessageQueue. If you are not sure, you can refer to other information by yourself.

Because the amount of code in the Framework layer of Handler is also relatively large, it is impossible to cover everything in one article, so I plan to start from the use of Handler, only the key nodes in-depth, so that not too into the details, but also grasp the whole process behind the Handler.

Use – sending of messages

To look at the use of Handler, let’s define a static inner class MyHandler as follows:

private static class MyHandler extends Handler { WeakReference<MainActivity> mMainActWeakRef; public MyHandler(MainActivity activity) { this.mMainActWeakRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); }}Copy the code

Then we’ll create a new MyHandler object, and we’ll implement our logic by sending a message to MyHandler.

// MainActivity, onCreate mMyHandler = new MyHandler(this); mMyHandler.sendMessage(new Message());Copy the code

We called the sendMessage method, which will eventually trigger a callback to the handleMessage method of mMyHandler with the same argument as the Message we sent. Let’s take a look at the sendMessage implementation

    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }  

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

Call Handler’s sendMessageDelayed method. Since delay is 0, the message will be sent at the current time obtained by systemclock.uptimemillis (). Then call enqueueMessage. The enqueueMessage method of MessageQueue is called internally. Which MessageQueue is sent to depends on the Looper bound to the Handler when it is initialized. MainLooper is initialized in the main method of ActivityThread.

Let’s take a look at the implementation of MessageQueue’s enqueueMessage method. Most of the key methods have been commented out.

boolean enqueueMessage(Message msg, long when) { // .... Synchronized (this) {// Sets the messageinUse MSG. MarkInUse (); MSG. When = when; // mMessages is the header of the Message queue. Note that the data structure is single linked list. boolean needWake; // If the queue is empty, or when is 0, or the current insertion time is less than the header message timeif (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue ifBlocked. // Insert the message into the header and check if it is currently blocked. If it is, set needWake to 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. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // Insert the new MSG into the appropriate position, sort for (;;) from smallest to largest by when field. { 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 ! // Wake Looper if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Therefore, inserting messages to the message queue is immediately inserted, sorted by the message when field. If the message is delayed, its when is the current time +delay time. Finally, there is a key method call nativeWake (mPtr), which wakes up Looper. The implementation will be left here for a moment and will be examined in detail later.

Message monitoring

To review Looper, we know that the Handler needs to bind to a Looper. By default, the Handler created for the main thread is bound to a Looper for the main thread. We can also use Looper for child threads

// Use Looper looper.prepare (); Looper.loop();Copy the code

The prepare method creates a new Looper and sets it to the sThreadLocal object. Let’s focus on the implementation of the loop method

    public static void loop() {/ /... Omit check code...for(;;) {Message MSG = queue.next(); // might blockif (msg == null) {
                // No message indicates that the message queue is quitting.
                return; } / /... Omit code... Try {/ / message distribution MSG. Target. DispatchMessage (MSG); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally {if(traceTag ! = 0) { Trace.traceEnd(traceTag); }} / /... Omit code... }}Copy the code

We see an infinite loop and then call queue.next() to get the next message from MessageQueue and possibly block, and we follow up with the implementation of the next method

    // MessageQueue.java
    Message nextFinal Long PTR = mPtr; final long PTR = mPtr; final long PTR = mPtr;if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for(;;) {// pollOnce, which blocks nativePollOnce(PTR, nextPollTimeoutMillis); Synchronized (this) {// Try to retrieve the next message.returnif found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {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(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        returnmsg; }}else{ // No more messages. nextPollTimeoutMillis = -1; } / /... Left out a bunch of code... Idle Handlers. // We only ever reach this code block during the first iteration.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); } } } } }Copy the code

The method is quite long, I have omitted the unimportant code, let’s first list the key processes, and then analyze them one by one.

  • Get mPtr, long, it’s a pointer, it’s a pointer to the Native layer MessageQueue object
  • Call nativePollOnce with mPtr and Poll timeout. This method blocks
  • Lock, and then pull a Message out of the Message queue, which has a bunch of logic that we’ll talk about later
  • Idle Handlers are executed if the message queue is already empty,

Initialization of mPtr

The mPtr is initialized in the MessageQueue constructor

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
Copy the code

NativeInit is a Jni method, follow up

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if(! nativeMessageQueue) { jniThrowRuntimeException(env,"Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
Copy the code

You can see that a NativeMessageQueue object (which inherits RefBase and is a smart pointer object) is new, then its reference count is +1 so it is not automatically reclaimed, and finally its object pointer address is returned. So we know that the mPtr in MessageQueue is the pointer to the Native layer NativeMessageQueue.

nativePollOnce

NativePollOnce is also a Jni method, so let’s take a look at the implementation

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);
}
Copy the code

The mPtr of the parameter is converted to a NativeMessageQueue pointer, and the pollOnce method is called to follow up

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; }}Copy the code

PollOnce of mLooper is called, which is the Looper object of the native layer, and we follow up

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; Result = pollInner(timeoutMillis); }}Copy the code

It calls its own pollInnner method

Int Looper::pollInner(int timeoutMillis) {struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // omit a block of code}Copy the code

Here we post only the key code. We see that the epoll_wait method is called, which can block, so that the dead loop in Looper we described earlier does not cause the main thread to idle all the time. There are several ways this method can return.

  1. An error occurred
  2. Exceeds the timeoutMills time we passed in
  3. The waiting mEpollFd is woken up, as described below

When the nativePollOnce method returns, proceed to the next step

Lock and retrieve the next message

Let’s go back to messagequeue.java and paste the code again. I have added comments directly to the code

final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message MSG = mMessages; If msg.target==null, this is a message barrier. If it is a message barrier, the entire message queue will be traversed, and if there is an asynchronous message, the first asynchronous message will be retrievedif(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
            } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {// If the message is not out of time, Looper continues to sleepif (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(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                    mMessages = msg.next;
                }
                msg.next = null;
                if (DEBUG) Log.v(TAG, "Returning message: "+ msg); msg.markInUse(); // Remove the message from the message queue and returnreturnmsg; }}elseNextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; }Copy the code

There are two concepts involved here that need to be explained

  • A message barrier, which is also a message but whose target is null, can be sent via MessageQueue’s postSyncBarrier method to insert a message barrier into the MessageQueue, and the normal messages behind it will wait until the barrier is removed before being processed. To clear it, call removeSyncBarrier.
  • Asynchronous messages are set in the setAsynchronous method of Message. Asynchronous messages are not affected by Message barriers, so if you want a Message to be executed immediately, you can send an asynchronous Message and insert it into the head of the Message queue.
    Message message = Message.obtain();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
        message.setAsynchronous(true);
    }
    mMyHandler.sendMessageAtFrontOfQueue(message);
Copy the code

Perform idle handlers

When no message in the message queue or message queue is now news barrier blocking the, will perform idle handlers, so its meaning lies in the fact that when there is no message queue task at execution time, give us a callback, let us have the chance to perform some non-urgent tasks, without fear of capturing the main thread of CPU execution time urgent tasks and Because the message queue is Idle at the moment, a typical use of Idle handlers is that we often use Idle handlers to perform non-urgent initialization tasks during App startup. You can add IdleHandler to MessageQueue by using the following code

    mMessageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            return true; }});Copy the code

The queueIdle value is meaningful. If false is returned, the IdleHandler will be removed after it is called back. If true, the IdleHandler will be called many times. However, IdleHandler is also executed in the main thread. If the task is too heavy, it may still block the message queue, resulting in delayed message processing.

Looper wake up – Fetch a message from a message queue

As mentioned earlier, Looper’s call to MessageQueue’s next method blocks. Its underlying implementation is epoll_WAIT, so if a new message comes in while blocking, Looper must be woken up to process the message. Wake up methods we talked about earlier, but let’s go back to MessageQueue’s enquque method

    if (needWake) {
        nativeWake(mPtr);
    }
Copy the code

It is also a native method, calling NativeMessageQueue’s Wake method, which ultimately calls Native layer Looper’s wake method.

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wakeUint64_t inc = 1; ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))); // omit judgment code}Copy the code

In Looper’s wake method, the write method is called. Its first argument is mWakeEventFd, which writes a 1 to the file corresponding to the mWakeEventFd descriptor. This write will apparently wake Looper from the epoll_wait wait. How does mWakeEventFd relate to our epoll_wait?

Let’s take a look at Looper’s constructor

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();
}
Copy the code

As you can see in the constructor, mWakeEventFd is initialized and the rebuildEpollLocked() method is called

void Looper::rebuildEpollLockedMEpollFd = epoll_create(EPOLL_SIZE_HINT); 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); // omit a block of code}Copy the code

After mEpollFd is created, the epoll_ctl method is called, which associates mEpollFd with mWakeEventFd. When data is written to mWakeEventFd, mEpollFd will wake up and the epoll_wait method will return. It should be clear at this point that our wake method writes a 1 to mWakeEventFd to wake up mEpollFd.

Message distribution

The distribution of messages is in the Lopper.java class. Let’s look at the implementation

    try {
        msg.target.dispatchMessage(msg);
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } finally {
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
Copy the code

The result is a call to the MSG target field for distribution, which we mentioned earlier is the Handler object

    public void dispatchMessage(Message msg) {
        if(msg.callback ! = null) { handleCallback(msg); }else {
            if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code

If the MSG has a callback and the Handler does not have a callback, then the Handler’s handleMessage method is called.

conclusion

Here, the whole principle of Handler in Android is finished. We start from the whole life cycle of Message, from Message creation, to joining the Message queue, to being processed by consumption and then recycled. We have gone deep into the native layer and understood the underlying technical principle.

To summarize

This paper mainly analyzes the following points

  1. How are messages created and then inserted into message queues
  2. Looper waits for messages in an infinite loop using the epoll_wait mechanism so that the main thread does not get stuck
  3. Looper is awakened by writing data to mWakeEventFd
  4. Message distribution mechanism, and there is an introduction to message barriers and asynchronous messages
  5. Analysis of callback time of idle Handlers

Understanding these principles can guide us to write better application code, and I hope this article has been helpful to you!

Finally, I want to post a code to demonstrate the use of epoll.

Why do I have this idea? Because I believe that most of you, including myself, are not familiar with Linux, it is easy to look at many system calls in the source code confused, so it is difficult to understand the source code. So I always want to have a chance to really operate a Linux programming, just this analysis of the Handler bottom using epoll is not very difficult, I just pull a demo to feel Linux programming, directly on the code (the environment is Ubuntu 14, the compiler is Clion).

The requirements are simple: the main process forks a child, the main process epoll_wait waits, the child sleeps 2s, wakes up the main process, and the program ends.

#include <sys/eventfd.h>
#include <sys/epoll.h>static const int EPOLL_SIZE_HINT = 8; Int main(void) {// create time descriptor int mEventFd = eventfd(0, 0);if (mEventFd == -1) {
        printf("create efd error!");
        exit(0); } // create the epoll descriptor int mEpollFd = epoll_create(EPOLL_SIZE_HINT); struct epoll_event eventItem; eventItem.events = EPOLLIN; eventItem.data.fd = mEventFd; // Bind event descriptors to epoll epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mEventFd, &eventItem); _pid_t pid = fork(); _pid_t pid = fork(); // Note that the fork method returns twice, ==0 on the child and >0 on the parentif(pid == 0) {// Child processprintf("child process sleep 2s\n"); // sleep 2s sleep(2); uint64_t event = 1; // Write 1 to mEventFd to wake up the parent process's epoll write(mEventFd, &event, sizeof(uint64_t);printf(child process write event finish! \n); }elseStruct epoll_event eventItems[EPOLL_SIZE_HINT * 100];printf("parent process start wating! \n"); Int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_SIZE_HINT*100, -1); Epoll_wait returns,printf("get event : %d\n", eventCount);
    }
    exit(0);
}

Copy the code

The console output is as follows: