Handler source learning understanding

1. Related class description

1. The Handler function

(1) To achieve thread switching, can be in A thread using B thread created Handler to send messages, and then in the B thread of the Handler handleMessage callback to receive A thread of the message.

Hanlder.postdelay (), sendMessageDelayed()

2.Message

The carrier of the Message, including the information such as the sent handler object, Message itself is a single linked list structure; A Message Pool is maintained internally, and using the message.obtain () method to obtain objects from the Message Pool allows us to avoid new objects in many cases, reducing memory overhead.

3.MessageQueue

MessageQueue: a priority queue containing a single linked list of messages in order message. when; You can add messages with enqueueMessage() and take them out with next(), but you have to work with a Handler and Looper to do this.

4.Looper

Create a Looper with looper.prepare () and call looper.loop () to poll the MessageQueue queue. Invoke the MessageQueue next () method until the Msg, then the callback Msg. Target. DispatchMessage (Msg); The realization of message extraction and distribution.

Ii. Principle analysis

We initialize a Handler in the member variable of the MainActivity, then the child thread sends a message via the Handler post/send MSG, and finally we call back the handleMessage(MSG: (Get our Message. Once you understand the process from sending to receiving, you’ll be able to understand the Handler principle. Here’s how it works:

1. Send a message

MessageQueue (); MessageQueue ();

 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
       // Handler enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage enqueneMessage
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

And then to call MessageQueue. EnqueueMessage (), the Message in the order of the when parameters of the Message since the childhood is inserted into the MessageQueue. A MessageQueue is a priority queue that stores messages internally as a single linked list. This completes the process of sending the message to the MessageQueue.

boolean enqueueMessage(Message msg, long when) {
           //、、、、、、
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // When == 0: insert MSG into the header of the queue
                // 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 the time of MSG. When to be inserted is less than the time of a message in the queue, the loop is broken
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false; }}// Insert MSG into the queue before this message
                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;
    }
Copy the code

2. Take out the message

We know that the MessageQueue MessageQueue is dead, and it will not retrieve a message by itself. So we need a Looper to get MessageQueue moving. To create a Looper, run the following command:

 //Looper.java    
 // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static void prepare(boolean quitAllowed) {
        // sthreadLocal.get () gets Looper that is not null
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

 //ThreadLocal.java
  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            // Place thread itself as key and looper as value in ThreadLocalMap
            map.set(this, value);
        else
            createMap(t, value);
    }
Copy the code

Looper.loop () ¶ Looper.loop () ¶ Looper.loop () ¶

//looper.java  
/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop(a) {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        / /...
        // Loop to fetch message
        for (;;) {
            Message msg = queue.next(); // Might block calls the next() method of MessageQueue to fetch the message
            if (msg == null) {MSG == null indicates that the message queue is exiting
                // No message indicates that the message queue is quitting.
                return;
            }
        / /...
            try {// Fetch the message and distribute it to the corresponding MSG. Target (handler)
                msg.target.dispatchMessage(msg);
              // 、、、、、
            } catch (Exception exception) {
              // 、、、、、
            } finally {
              // 、、、、、
            }
           // 、、、、、 reset the properties and reclaim the message to the reuse poolmsg.recycleUnchecked(); }}//Message.java
  void recycleUnchecked(a) {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this; sPoolSize++; }}}Copy the code

3.MessageQueue’s next() method

If there are asynchronous messages, all asynchronous messages are put in the front of the queue for priority execution. Then, synchronous messages are obtained and distributed to the corresponding handler. When there is no message to execute in the MessageQueue, that is, when the MessageQueue is idle, If there is an idelHandler, the idelHandler task will be executed and handled through idler.queueIdle().

  //MessageQueue.java
Message next(a) {
    / /...
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
            // When there is no message, the main thread sleeps
            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) {
                    // MSG. Target == null for asynchronous messages. All asynchronous messages are found and placed at the head of the queue
                    // 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) {// Synchronize message processing
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        // Sleep for a while before the next message is executed
                        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.
                    // Sleep without news
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    PendingIdleHandlerCount: pendingIdleHandlerCount: pendingIdleHandlerCount
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    / / create IdleHandler []
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the 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 {
                    // Execute IdleHandle[] for each queueIdle(), Return true to keep your idle handler active
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) {// The mIdleHandlers are deleted
                    synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0; }}Copy the code

Three. Common Problems

Through the above source code analysis, understand the working principle of Handler, according to the principle can answer the following several classic questions:

1. How many handlers does a thread have?

It is possible to have multiple handlers because multiple handlers can be new in one thread, and when a handler sends a message, it assigns that handler to message-target

2. How many Looper does a thread have? How to guarantee it?

There can only be one, otherwise an exception will be thrown

3. Why does Handler memory leak? Why don’t other inner classes talk about this?

Handler when the root cause of memory leaks is to delay Message, because the Handler to send a delayed Message to MessageQueue, MessageQueue held in possession in the Message Handler reference, The Handler, as the inner class of the Activity, holds a reference to the Activity. Therefore, when the Activity is destroyed, the Message of the MessageQueue cannot be released, resulting in the Activity cannot be released, resulting in a memory leak

4. Why can the main thread use new Handler? What should we do to prepare a new Handler in a child thread?

Because ActivityThread will automatically create Looper for us when our APP starts. Looper.prepare(), looper.loop (), looper.loop ()

//ActivityThread.java    
public static void main(String[] args) {
  

        Looper.prepareMainLooper();
         / /...
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

5. For Looper maintained by child threads, what is the solution when there is no message in the message queue? What’s the use?

The new Handler in the child thread should call looper.prepare (), looper.loop (), and manually terminate Looper by calling looper.quit (),LooperquitSafely() at the end. Otherwise, the child thread will always be stuck in the blocked state

6. Now that there can be multiple Handler to MessageQueue add data (message when the Handler can in different threads), internal how to ensure that his thread safe?

 boolean enqueueMessage(Message msg, long when) {
       / /... Synchronized ensures thread safety
        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;
            }
            / /...
        }
Copy the code

7. How should we create Message when we use it?

Message.obtain()

8. What happens to message queues after postDelay with Handler?

Insert Message as message.when

9. Why Looper loops don’t cause the app to get stuck

Which death cycle, no message will be hibernates not stuck Linux calling the underlying epoll mechanism to realize; When the application is stuck, it is caused by ANR; It’s a different concept

ANR: If the user does not respond to the input event or touch the screen for 5S or the broadcast does not finish within 10S, each lifecycle function of the Service cannot finish processing within a specific time (20 seconds)