What is the Handler

An overview of the

A basic component provided by the Android Framework for interthread communication. The main thread updates the UI by passing more detailed messages to the child thread.

Android main thread UI, only main thread update. If multiple threads can update, it is necessary to “lock”, rather than “single thread message queue mechanism”.

The main thread maintains a loop internally. When there is no message, the loop is blocked. A new message (or blocking timeout) wakes up and the new incoming message is processed.

Java layer Handler

The entire handler can be roughly divided into three processes

  • Build and poll messages
  • Send message and wake up wait
  • Distribute processing messages

Our first sense with Handler is to send messages, so we start with sending messages.

1. Send message and wake up wait

Let’s start with sending messages, which is the easiest way to send a message.

// Build a handler to handle message
val handler = object : Handler() {
     override fun dispatchMessage(msg: Message) {
          Log.d("alvin"."msg.what:${msg.what}")}}var msg = Message().also { it.what = 1 }
handler.sendMessageDelayed(msg,1000L)
Copy the code

A little bit of code tracking shows that there are a lot of sendMessage methods, all bundled into one method

//Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;// This distribution is used to omit some other code
        return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Then call the MessageQueue. EnqueueMessage (Message MSG, long the when), the code below, explain, we have a singly linked list Message, the Message is next to the next Message, And when this message triggers. Singly linked lists are ordered, the smaller the “WHEN” is, the higher it goes. According to when, the new message is inserted in the appropriate position, and if it happens to be inserted at the head of the queue, nativeWake needs to be woken up. So it comes down to two things: joining the team and waking up.

//MessageQueue. Java ignores some code and only cares about the logic we have reached this time
boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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 {
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when)  break;
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            if (needWake) nativeWake(mPtr);
        }
        return true;
    }
Copy the code

2. Build and poll messages

We’ll focus on building a Handler that will call messagequeue.java

 val handler = object : Handler()
 -->mLooper = Looper.myLooper();
	-->sThreadLocal.get()
    
ActivityTread.main()
-->Looper.prepareMainLooper()// Build the Looper for the current thread
   -->Looper.prepare()
 	  -->new Looper(quitAllowed)
    	 -->mQueue = new MessageQueue(quitAllowed);
			-->mPtr = nativeInit()
-->Looper.loop();// Start polling
Copy the code

Where does the main thread Looper come from, as long as the application thread is started, we have

  • Construct Looper for the current thread, looper.prepareMainLooper ()
  • Start polling, looper.loop ()

2.1. Create and store Looper to thread-local variables

//ActivityTread.java
class ActivityTread{
	public static void main(String[] args) { Looper.prepareMainLooper(); }}// looper.java, omits some code
class Looper{
    /** * You can see that Looper in sThreadLocal can only be set once */
    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper(a) {
        prepare(false);
        synchronized (Looper.class) {
             if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}}Copy the code

2.2. Start polling

Looper.loop() omits some code. That’s where the block is. NativePollOnce (PTR, nextPollTimeoutMillis). Instead of blocking with Wait /notify in Java, Linux’s epoll mechanism blocks because events on the native side need to be processed. When there is no message, it blocks and goes to sleep to release CPU resources. When there is a message, it wakes up again. Epoll Reference: The nature of epoll

class Looper{
    public static void loop(a) {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            // 1. Block until next returns a value.
            Message msg = queue.next(); 
            //2. Message distribution processing mentioned latermsg.target.dispatchMessage(msg); }}}class MessageQueue {
    Message next(a) {
        /** = 0, no blockage, return immediately; * When greater than 0, the maximum blocking wait time, during which a new message comes in, may return immediately (execute immediately); * is equal to -1, when there is no message, it will always block; * /
        int nextPollTimeoutMillis = 0;
        for (;;) {
            // Block here, execute the following code when timed out or woken up
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! =null) {
                    if (now < msg.when) {
                    	// We find that the execution time of the first message is not up, so we calculate the timeout for the next nativePollOnce
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // get a message. Retrieve the message and reuse the next message as the header
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }
}
Copy the code

3. Send processing messages

In the stars. The loop () performed in the MSG. Target. DispatchMessage (MSG); MSG. Target is our Handler object.

As early as the Handler. EnqueueMessage (MSG,…). “, we execute MSG. Target = this;

class Handler{
    public void dispatchMessage(@NonNull Message msg) {
        // MSG. Callback traces the code. Post (Runnable) builds a message with a callback
        if(msg.callback ! =null) {
            handleCallback(msg);//-->message.callback.run();
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; }}// We do not set mCallback to handler, so we execute handleMessagehandleMessage(msg); }}// Post (Runnable), when building a message with callback
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        returnm; }}Copy the code

4. Summarize the Java layer

Although the main process is clear, we omitted a lot of code, which can be explored as questions

  • What a SyncBarrier is and what it does
  • Looper.loop() will be blocked if there is no message.
  • Message.obtain(), the Message cache pool
  • Handler.removeCallbacksAndMessages()

Native layer Handler

News polling mechanism is through the MessageQueue. NativePollOnce () hangs and MessageQueue. NativeWake sensei. MessageQueue saves NativeMessageQueue objects through mPtr variables, which makes MessageQueue become the hub of Java layer and Native layer, and can process both upper-layer and Native layer messages. The corresponding diagram of Java layer and Native layer is listed below. Note that the Java layer and the C++ layer respectively implement a set of Handler mechanism code.

Looper.prepare()

New Looper()–>new MessageQueue()–>mPtr = MessageQueue. NativeInit ()–> native: Android_os_MessageQueue_nativeInit ()–>new NativeMessageQueue()–>new Looper(false) finally goes to the looper.cpp constructor

Looper::Looper(bool allowNonCallbacks)
    rebuildEpollLocked(a); }void Looper::rebuildEpollLocked(a) {
    struct epoll_event eventItem;
    memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;// Listen for mWakeEventFd to write data.
    eventItem.data.fd = mWakeEventFd.get(a);// Create an eventPoll object that returns an EPFD, the eventPoll handle.
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
}
Copy the code

Epoll_ctl () is not a poll_ctl().

Epoll mechanism in Android Handler

If this article can’t explain the nature of epoll, come and strangle me!

Looper.loop()

From Java layer analysis we get stars. The loop () – > MessageQueue. NativePollOnce (mPtr, timeout) into the native layer: android_os_MessageQueue_nativePollOnce()–>NativeMessageQueue::pollOnce() –>Looper::pollOnce()–>Looper::pollInner() A run into Looper::pollInner()

int Looper::pollInner(int timeoutMillis) {
    //1. TimeoutMillis handle, omit...
    //2. Enable wait and listen for mWakeEventFd write event
    	//events represents an array of events that are returned to process;
    	// maxEvents indicates the maximum number of events that can be processed at a time.
    	//timeout: specifies the timeout period for waiting for I/OS. -1 indicates that the I/O will be blocked until the next I/O is woken up. If the value is greater than 0, the I/O will be woken up after the specified time
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    //3. Execute the message added by Native layer first. The logic is similar to that of Java layer to process messages.
    / / 4. Go back to the Java layer, MessageQueue. NativePollOnce (), execute the Java layer of information extraction.
}
Copy the code

Handler.sendMessage()

Based on the previous Java layer analysis, Enter the MessageQueue. NativeWake () Android_os_MessageQueue_nativeWake ()–>NativeMessageQueue:: Wake()–> Looper::wake()

void Looper::wake(a) {
    uint64_t inc = 1;
    // Write 1 to mWakeEventFd, then go to Looper::pollInner(), invoke "epoll_wait", and continue execution.
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}
Copy the code
int Looper::pollInner(int timeoutMillis) {
    //1. TimeoutMillis handle, omit...
    //2. Enable wait, listen for mWakeEventFd write event, omit...
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    //Looper::wake() to this point.
    //3. Execute the message added by Native layer first. The logic is similar to that of Java layer to process messages
     while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
           		sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                handler->handleMessage(message);
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break; }}/ / 4. Go back to the Java layer, MessageQueue. NativePollOnce (), execute the Java layer of information extraction.
}
Copy the code

Native layer summary

Similar to the Java layer, but at the same time we omit a lot of code and continue to look at the code with questions

  • Which is not only used to handle Handler, there are other role, see MessageQueue. AddOnFileDescriptorEventListener code
  • ALooper. CPP is what

reference

Change positions and look at Handler with the question

Android Messaging 1-Handler(Java Layer)

Epoll mechanism in Android Handler

If this article can’t explain the nature of epoll, come and strangle me!