This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Handler

Handler is the object associated with the thread MessageQueue queue to send and process messages and Runnable.

1. Send Runnable as follows:

  • post(Runnable)
  • postAtTime(Runnable, long)
  • postDelayed(Runnable, long)

2. Send a Message as follows:

  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message, long)
  • sendMessageDelayed(Message, long)

Note that the handler sending the Runnable object method actually calls the SendMessage method. To view the source code directly, Runnable is used as an input parameter to getPostMessage, which is converted to the Message object, and Runnable is assigned to the Message callback.

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

3.dispatchMessage

Handler’s handleMessage is invoked via dispatchMessage. The handler receives messages in three ways.

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
  • ifmsgthecallBackNot null, callMessagetheRunnableThe run method
    private static void handleCallback(Message message) {
        message.callback.run();
    }
Copy the code
  • ifHandlerthemCallbackThe interface callback is not null
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
Copy the code
  • The last callhandleMessage
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
Copy the code

4. To receive the Message

  • handleMessage(Message)

So there is no receiving Runnable method, which explains why the sent Runnable is converted to Message.

5. Attention!!

The mLooper required to create a Handler in a child thread cannot be null. This should all be clear: The Handler constructor checks whether the mLooper is null and throws an exception if it is. Looper is created by calling looper.prepare () and instantiating Looper MessageQueue and Thread before creating Handler in the child Thread.

Looper

Looper is the class used to run message queues in Threads. Thread is not associated with Looper by default. 1.prepare

Call prepare in the thread to run the loop. Sthreadlocal.get () gets Looper, or new Looper() if null.

public static void prepare() { prepare(true); } 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)); }Copy the code

2.loop

Let Looper loop through Meessage in MessageQueue. The Loop method is long and basically has for (;;) in it. Thread blocking keeps sending messages from the queue to the Hanlder for processing. Specific in MSG. Target. DispatchMessage (MSG). MSG. Target object is the Hanlder. Look at this

for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }... Omit part of the code final long slowDispatchThresholdMs = me. MSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag ! = 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }}... Msg.recycleunchecked (); }Copy the code

3.quit

Looper calls the loop method to go into an infinite loop, so how can Looper stop polling MessageQueue? Looper provides quit and quitSafely to quit Looper.

  • quit

Quit is insecure because some messages may exist before the Looer ends and cannot be distributed in the MessageQueue. *

Using this method may be unsafe because some messages may not be delivered before the looper terminates. Consider using {@link #quitSafely} instead to ensure that all pending work is completed in an orderly manner.

  • quitSafely

Quit is not safe, so we use the quitSafely. Ensure that messages in MessageQueue are distributed in a timely manner. However, delayed messages cannot be sent after quit. Looper calls MessageQueue’s quit.

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}
Copy the code

MessageQueue

Now, MessageQueue. So let’s call Looper to quit before we start. MessageQueue quit as long as said removeAllFutureMessagesLocked and removeAllMessagesLocked safe control. 1.quit

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); }}Copy the code
  • whenLoopercallquitUnsafe exitremoveAllMessagesLocked, the loopMessageQueueWill exit the previous queueMessageClear them all.
private void removeAllMessagesLocked() { Message p = mMessages; while (p ! = null) { Message n = p.next; p.recycleUnchecked(); p = n; } mMessages = null; }Copy the code
  • whenLoopercallquitSafelyperformremoveAllFutureMessagesLocked, get the currentmMessagesthewhenandSystemClock.uptimeMillis()To obtain thebootMilliseconds from the beginning to the present. ifMessageMore time thanquitTime to perform an unsafe exitquitUnder theremoveAllMessagesLocked. Otherwise a loop will be enteredMessageQueueTo perform theMessageHand it out untilMessageQueueEmpty or time out (when > now).

Ps: So the delay in sending a Message or Runnable by the handler comes into play here.

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
Copy the code

2.enqueueMessage

We saw earlier that when we send a Message, we end up executing the enqueueMessage method of a queue.

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

MessageQueue does not arrange messages in an array; MessageQueue only holds the current mMessages. A new Message added to the queue determines its position in the queue based on its WHEN. You can also see that if the Thread is dead, an illegal exception is thrown and the sent Message is reclaimed.

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

Message

And finally, Message. The main Message member parameters are: long WHEN, Bundle data, Handler target, Runnable callback, and Message Next. You can see that Message has no precursor and only one successor. So MessageQueue only stores the current mMessages and can find the next Message. 1.Runnable Message has the member parameter Runnable. Remember that the Handler Post was a Runnable and then converted to a Message, and the Runnable that was the input parameter was there. If the Runnable of the Message is not empty, the dispathMessage of the Handler will go this way. Handler is Post, and Runnable is run when received.

The last

The whole point is to figure out Handler, Looper, MessageQueue, and Message. The Handler acts as the receiver and sender of the Message and inserts the Message into the MessageQueue. Looper acts as a Handler, picking up messages from MessageQueue and dropping them to handlers. So why does Handler, as Message receiver and sender, go all the way around Queue and Looper? Thread is also involved. Since the topic is mainly about the Handler mechanism, Thread will be covered separately next time. Handler is used to solve interprocess communication problems and handle events asynchronously, creating child threads to do asynchronous operations, and returning the desired content to the main thread to achieve the purpose of not blocking the UI thread.

class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }}Copy the code

Finish with a picture of the Handler mechanism in action that is easier to understand. The source of the image is linked at the end of this article

reference

  • Developer.android.com/reference/a…
  • Developer.android.com/reference/a…
  • Android.xsoftlab.net/reference/a…
  • Blog.mindorks.com/android-cor…