Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article will also participate in the “Digitalstar Project” to win a creative gift package and challenge the creative incentive money!

We all know that we need to start the Looper thread by ourselves and manually call quit() or quitSafely() to stop the Looper round at the end of the task. But for the details seem not to have thought carefully, take five minutes to learn briefly!

  • Why did Looper manually quit?
  • How to deal with Message when quit?
  • What are the optimizations for the quitSafely?
  • Does the main thread Looper need quit?

Why should the Looper thread quit manually?

The thread that creates the Looper and executes loop() needs to manually call quit at the end of the task.

Otherwise, the thread will remain runnable due to loop() polling and CPU resources will not be released. It is more likely to cause a memory leak because Thread is holding out-of-life instances as GC Root.

When quit is called, Looper no longer waits because there is no Message, but instead fetches tonull, which triggers the exit of the round robin loop.

// Looper.java
    public static void loop(a) {...for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // Exit with null
                return; }... }}Copy the code

How to deal with Message when quit?

Much of Looper’s processing is actually done by MessageQueue, including Looper#quit() here. It actually calls the MessageQueue function of the same name, quit(Boolean), and specifies the safe parameter as false.

// Looper.java
    public void quit(a) {
        // The default is an unsafe exit
        mQueue.quit(false);
    }
Copy the code

MessageQueue#quit() performs a few simple tasks, including marking the exit, emptying all outstanding mesages, and finally waking up the thread.

// MessageQueue.java
    void quit(boolean safe) {...synchronized (this) {... mQuitting =true; / / tag quitting

            // An unsafe exit will reclaim all messages in the queue and empty the queue
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // Wake up the threadnativeWake(mPtr); }}Copy the code
  1. Exit flags will result in subsequentsendMessage()postRunnable()Invalid, return directlyfalse.
// MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {...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();
                // Contract/formspapers flag Causes subsequent Message send failures
                return false; }... }return true;
    }
Copy the code
  1. The default policy is to empty the queue of all messages, including messages that arrive at exactly the right timefriendlysecurity.
// MessageQueue.java
    // All in one: leave the queue whether or not when arrives
    // Messages that should be executed at the current moment may also be culled and cannot be executed
    private void removeAllMessagesLocked(a) {
        Message p = mMessages;
        while(p ! =null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
Copy the code
  1. Finally, the thread wakes up and enters the next loop to read the queue, which returns NULL because there is no Message in the queue.
// MessageQueue.java
    Message next(a) {...for(;;) {...synchronized (this) {...// There is no Message in the queue
                // Will return NULL directly for the presence of the row exit flag
                if (mQuitting) {
                    dispose();
                    return null; }... }... }}Copy the code
  1. Loop () gets null Message, the loop exits, and the thread terminates.

What are the optimizations for the quitSafely?

It is well known that the SDK recommends using the quitSafely() to terminate Looper because it will only discard messages whose execution time is later than the current call time.

This ensures that the messages that meet the execution time condition remain in the queue when the quitSafely is called and exit the polling only after they are all executed.

  1. Call MessageQueue#quit() and specify the safe parameter astrue.
// Looper.java
    public void quitSafely(a) {
        mQueue.quit(true);
    }
Copy the code
  1. When the security quit is calledremoveAllFutureMessagesLocked().
// MessageQueue.java
    void quit(boolean safe) {
        synchronized (this) {
            if (safe) {
                // Safely exit the call
                removeAllFutureMessagesLocked();
            } else{ removeAllMessagesLocked(); } nativeWake(mPtr); }}Copy the code

As the name suggests, it simply removes future messages. Compared to the present moment, not all things that have not been implemented are called the future, don’t get me wrong!

// MessageQueue.java
    private void removeAllFutureMessagesLocked(a) {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if(p ! =null) {
            // If the execution time of the queue header Message is still later than the current time, then all are cleared
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                // Otherwise, the queue is traversed and messages to be culled are filtered
                Message n;
                for (;;) {
                    n = p.next;
                    // If there is no Message later than that, it does not need to be removed
                    if (n == null) {
                        return;
                    }
                    // Find the last Message in the queue that was later than the current time
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                // All messages after the first Message are queued
                p.next = null;
                // Reclaim the last Message that was later than the current moment and subsequent messages
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while(n ! =null); }}}Copy the code
  1. The next() loop that wakes up the thread after removing future messages retrieves any messages left in the queue for processing.
    Message next(a) {...for(;;) {...synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null; Message msg = mMessages; .if(msg ! =null) {
                    // Message in queue earlier than current time, else
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;                        
                        // Message exits the queue and updates the pointer
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            // The queue leader moves back one node
                            mMessages = msg.next;
                        }

                        // Get the Message and pass it to Looper for callback
                        msg.next = null;
                        msg.markInUse();
                        returnmsg; }}else{... }// There are still messages in the queue
                // The exit flag will not return null for now
                if (mQuitting) {
                    dispose();
                    return null; }}... }}Copy the code
  1. When all the remaining messages are executed, next() will not fetch Message, and eventually becausequittingFlag returns null, which triggers the exit of loop().

Does the main thread Looper need quit?

The main thread ActivityThread creates Looper with a flag that disallows quit, that is, quit cannot be called manually.

// Looper.java
    public static void prepareMainLooper(a) {
        prepare(false); . }private static void prepare(boolean quitAllowed) {... sThreadLocal.set(new Looper(quitAllowed));
    }

    // Main Looper is initialized with a disallowed exit
    private Looper(boolean quitAllowed) {
        mQueue = newMessageQueue(quitAllowed); . }Copy the code

If quit() is forcibly called in the main thread, the following exception occurs:

java.lang.IllegalStateException: Main thread not allowed to quit.

// MessageQueue.java
    void quit(boolean safe) {
        if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit."); }... }Copy the code
  • Does the main thread need quit?

    No, App is recycled directly by AMS when memory is low.

  • The reason why you don’t need quit is that?

    The main thread is important for managing the life cycle of components such as ContentProviders, activities, and Services. Even when one component dies, it still exists to schedule other components.

    In other words, the ActivityThread is scoped beyond these components and should not be handled by them. For example, if an Activity destroys, the ActivityThread still handles transactions for other activities or services and other components, and cannot terminate.

conclusion

For the purpose of reclaiming resources or avoiding memory leaks, you should manually quit after Thread completes the task. To make sure that any Message that could have been executed safely when quit is executed, call quitSafely. Also understand the importance of the main thread Looper, it does not need and cannot be manually quit!