Source code analysis: Handler mechanism

1. Four major components

  • message: message.

  • MessageQueue: The message queue is responsible for the storage and management of messages, and is responsible for managing the Message sent by the Handler. Reading will automatically delete messages, and there are advantages in singly linked list maintenance, insertion and deletion. In its next() method, there will be an infinite loop, constantly judging whether there is a message, and if there is, return the message and remove it.

  • Looper: The message circulator is responsible for the distribution of associated threads and messages. Under this thread, the Message is obtained from the MessageQueue and distributed to the Handler. When the Looper is created, a MessageQueue will be created. When the loop() method is called, the message loop starts, and it will continue Call the next() method of messageQueue, process it when there is a message, otherwise block in the next() method of messageQueue. When Looper's quit() is called, messageQueue's quit() will be called. At this time, next() will return null, and then the loop() method will also exit.

  • Handler: Message processor, responsible for sending and processing messages, facing developers, providing API, and hiding the details behind the implementation

2. The circulation process of the message

  1.  Handler sends the message Message to the message queue MessageQueue through sendMessage().
  2. Looper continuously extracts the message of the trigger condition through loop(), and hands the message to the corresponding target handler for processing.
  3. The target handler calls its own handleMessage() method to process the Message.

3. How many Loopers does a thread have? How to guarantee?

        A thread has only one Looper, and Looper.loop() is started by the thread

        Looper in the main thread is created in the main function of ActivityThread in AMS, call Looper.prepareMainLooper(); and then call prepare(), which has a static final sThreadLocal to set a Looper (the constructor of the Looper object is a private , can only be created internally), the ThreadLocalMap of ThreadLocal uses the current thread as the key, and the created Looper is stored as a key-value pair of value, and sThreadLocal will do a test before setting, and if the ThreadLocalMap has a value, an exception will be thrown, so It is guaranteed that there is only one Looper in a thread

 1. Looper in the main thread is created in the main function of ActivityThread in AMS

   public static void main(String[] args) {
        ……
        Looper.prepareMainLooper();
        ……
        Looper.loop();
    }

2. Call prepare() in Looper.prepareMainLooper()

3. In the prepare method, go to a new private Looper constructor to create a Looper object

   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));
    }

4. Set Looper as value and current thread as key to ThreadLocalMap by set method

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

5. Since it is called in the main thread, the main thread and Looper are associated through the relationship between the ThreadLocalMap key-value pair 

6. sThreadLocal is modified by static final, which is unique in the entire app, and checks whether sThreadLocal is empty in the prepare method, and throws an exception directly if it is not empty. A Looper can only be created once with a thread

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

7. This ensures the one-to-one correspondence between threads and Looper

4. How many handlers does a thread have?

Countless Handlers can be created, but the message queues they use are the same, that is, the same Looper

5. What is the reason for Handler memory leak? Why don't other inner classes have this problem

        Create a handler of an inner class, which is equivalent to an anonymous inner class. The handler passes the message to the MessageQueue through enqueueMessage() through send or post, and the MessageQueue will hold the message. In the enqueueMessage method, this will be assigned to the target of msg. The msg holds the handler, and the handler is an anonymous internal class that holds the object of the external class, that is, the activity. The msg has not been executed and has always existed in the MessageQueue, so the activity cannot be destroyed, and the objects inside will not is processed, causing a memory leak 

Solution:        

  • Declare the handler as a static class, and use weak references to hold the handler (the activity may not be found when using it)
  •  Destroy all messages in the activity's destroy 

6. Why can new handler be used in the main thread, and how to operate in the sub-thread

        Because Looper.prepareMainLooper() has been called in the main function of ActivityThread; the initialization of Looper is completed, so the new handler can be directly in the main thread. 

7. What is the solution for the looper maintained in the sub-thread and when there is no message in the message queue? What is the use? What about the main thread?

         The child thread is in the for infinite loop, calling the nativePollOnce method is in the block state, which means that Looper.loop() is in the blcok state and has been executing, causing the run method to always execute, which means that the child thread is always executing, thread-related The memory will not be released, resulting in a memory leak

        Solution:

               Call looper.quitSafely() -> MessageQueue.quit -> MessageQueue#removeAllMessagesLocked: Remove all messages, and call nativeWake to wake up the for infinite loop, execute until msg == null, then exit the for loop, and the thread will exit up

        The main thread is not allowed to exit

MessageQueue#enqueueMessage saves messages

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            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 {
                // 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;
                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);
            }
        }
        return true;
    }

MessageQueue#quit remove message, wake up


    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

MessageQueue#next key code to fetch messages

  Message next() {
        ……

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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;
                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) {
                    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();
                        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;
                }
              ……
    }

8. Since multiple handlers can be stored to add data to MessageQueue, and each handler may be in a different thread when sending a message, how does it ensure thread safety internally? What about news?

        MessageQueue is initialized with final and cannot be modified after initialization. MessageQueue can only be created by Looper. Threads correspond to Looper one-to-one, and Looper corresponds to MessageQueue one-to-one. Synchronized (this) locks the only MessageQueue to ensure Thread safety, the same is true for fetching messages, preventing adding messages when fetching messages. Access to messages is guaranteed to be unique 

9. How to create when using message?

        The amount of messages is large, direct new messages will lead to frequent creation and destruction, frequent GC, and every GC will bring STW (stop all threads) problems, resulting in stuck, and memory fragmentation, resulting in OOM

        Use obtain() to create, use flyweight mode, reuse Message, and reduce memory consumption:

  • Through the static method Message.obtain() of Message;

  • Through the public method handler.obtainMessage() of Handler.

    private static int sPoolSize = 0;
    Message next;
    private static final Object sPoolSync = new Object();
    private static Message sPool;

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

10. How is the message blocking of the handler realized? Why does the main thread not block?

       It involves the Linux pipe/epoll mechanism. Simply put, when the MessageQueue of the main thread has no news, it will be blocked in the nativePollOnce() method in the queue.next() of the loop. At this time, the main thread will release CPU resources and enter the dormant state. Until the next message arrives or a transaction occurs, wake up the main thread to work by writing data to the write end of the pipe. The epoll mechanism used here is an IO multiplexing mechanism that can monitor multiple descriptors at the same time. When a descriptor is ready (ready or write ready), the corresponding program is immediately notified to read or write. The essence is Synchronous I/O, that is, reading and writing are blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources.

        Each event is a message, including service, privode, input, button, click event, broadcast, etc., and is finally managed by the handler. The handler will block if there is no message processing. When the main thread is blocked, the main thread has nothing to do , sleep, when a message is generated, enqueueMessage a Message in MessageQueue, and then call nativeWake to wake up the waiting Looper. After waking up, queue.next wakes up at this time 

         And ANR means that after a message is sent out, the message is not processed in time, and the time-consuming exceeds the limit (there is no response to input events within 5s, such as buttons, screen touches, etc.; the broadcast is not completed within 10s), and ANR will appear, and an ANR will be reported. , encapsulated into a message, throw it to the handler for processing, and trigger a dialog 

        AMS management mechanism:

                Each application exists in its own virtual machine, that is to say, it has its own main function.

                Startup process: launcher -> zygote -> art application -> activityThread -> main

                All lifecycle functions of all applications (including activity and service) run in this Looper and exist in the form of message

Summarize:

        Application stuck ANR has nothing to do with this Looper at all. When the application has no message processing, it is sleeping and releases the thread; stuck is ANR, and message blocking is sleep

Eleven, the correct posture of playing Toast in the sub-thread

In essence, because the implementation of Toast depends on Handler, it can be modified according to the requirements of sub-threads using Handler. The same is true for Dialog.

12. How is the delay of handler postDelay realized?

handler.postDelay does not wait for a certain period of time before putting it into the MessageQueue, but directly enters the MessageQueue, which is realized by combining the chronological arrangement and wake-up of the MessageQueue.

Guess you like

Origin blog.csdn.net/weixin_42277946/article/details/130308898