When I work, I found myself handy for a lot of things to use, the principle and mechanism also recited the familiar, but a question about the source of the brain…. Watt! So recently ready to start from scratch to learn the source code, learning great minds excellent!

This article is the source analysis of the Handler mechanism, the purpose is to be able to understand the Handler mechanism from the point of view of the source code a little bit, there will be a lot of source code, so it will be boring, but as long as carefully read, I believe you will have a clearer understanding of the implementation of the Handler mechanism is very simple to use!

private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // Process the received message}};Copy the code

After initialization, after the child thread completes time-consuming operations, use

 handler.sendEmptyMessage(what)
Copy the code

Starting with the sendEmptyMessage method, parse the handler’s workflow step by step — notice that the handler starts sending messages to the message queue;

After we click on it, we will find that both sendEmptyMessage and sendMessage are finally calling sendMessageAtTime to put the sent message into the messgeQueue. Note that in the sendMessageDelayed method, the delayMillis delay time has been converted to SystemClock. UptimeMillis () + delayMillis, which refers to the time the message was taken out for execution, This will be more important in MessageQueue

Public final Boolean sendEmptyMessage(int what){// Call sendEmptyMessageDelayed directlyreturnsendEmptyMessageDelayed(what, 0); } // Is called by the sendEmptyMessage method, delayMillis is 0, Public final Boolean sendEmptyMessageDelayed(int What, Long delayMillis) {// Copy the parameters and convert them to Message Message MSG = message.obtain (); msg.what = what;return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if(delayMillis < 0) { delayMillis = 0; } // Here systemclock.uptimemillis () + delayMillis is used to calculate the execution time of the messagereturnsendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } // Eventually, all methods of sending messages will end up here, Public Boolean sendMessageAtTime(Message MSG, long uptimeMillis) {// Where does mQueue come from? MessageQueue queue = mQueue;if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
Copy the code

In the sendMessageAtTime method, the message is put into the MessageQueue in the familiar handler mechanism; Now let’s examine the sendMessageAtTime method, which mainly executes a non-null judgment for MessageQueue, and then directly executes the enqueueMessage method. Where does mQueue come from? For the sake of logical coherence, I’m not going to analyze it here, I’m going to analyze it down here, and I’m going to analyze it next to enqueueMessage;

Private Boolean enqueueMessage(MessageQueue queue, Message MSG, long uptimeMillis) {// Assign Hanlder to Message, Target = this; hanlder MSG. Target = this;if(mAsynchronous) {// Whether asynchronous, here isfalseMSG. SetAsynchronous (asynchronous) does not execute MSG. SetAsynchronous (asynchronous)true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

The enqueueMessage method is also simple. It assigns the Hanlder to Message, which is used to receive the Message when it is distributed, and then passes the Message to messageQueue for processing. At this point, we start to enter the messageQueue — of course, it’s not that the Handler source code has been analyzed, but I’m just following the code step by step so that I can see all the paths that the Handler mechanism has taken. In order to finally be able to fully understand the Handler message mechanism, will eventually come back to the Handler;

MessageQueue (MessageQueue) : MessageQueue (MessageQueue)

EnqueueMessage (Message MSG, long when); enqueueMessage(Message MSG, long when);

Boolean enqueueMessage(Message MSG, long when) {// MSG. Target Is before the Handler into the MessageQueue enqueueMessage assignment in the method, is the current Handlerif (msg.target == null) {
           throw new IllegalArgumentException("Message must have a target.");
       }
       if(MSG. IsInUse ()) {// If the current Message is executed, throw new IllegalStateException(MSG +) twice" This message is already in use.");
       }
synchronized (this) {
           if(McOntract) {// McOntract is always defaultfalseOnly if the quit method is executed and the MessageQueue can be safely exited will be assigned totrueThe main thread cannot be exited, so it will always befalseE = new IllegalStateException(MSG. Target +)" sending message to a Handler on a dead thread");
               Log.w(TAG, e.getMessage(), e);
               msg.recycle();
               return false; } // Start processing incoming messages MSG. MarkInUse (); // Mark the message as already queued MSG. When = when; Message p = mMessages; boolean needWake; // If mMessages is Null (p == Null), then there is no cache of messages in the message queue and you can put it directly to the top of the queue and wake up the queue that is blocking //when==0, // if mMessages! When < p.when = =null; when < p.when = =null; when < p.when; when < p.when; MMessages is a linked list structure, and mMessages is at the top of the list)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{// asynchronous (); // Asynchronous(); // asynchronous (); // asynchronous (); Is this familiar? In the Handler enqueueMessage method, there is a line of code that says MSG. SetAsynchronous (asynchronous)true); NeedWake = mBlocked && p.target == null && msg.isasynchronous (); Message prev; // The next loop inserts incoming messages into the message queue at the specified position based on the execution time whenfor (;;) {
                   prev = p;
                   p = p.next;
                   if(p = = null | | the when < p.w hen) {/ / here, (the when < p.w hen) said the latest incoming in waiting for news of the inserted into the message queue execution time is earlier than the current execution time, the news will be the latest incoming messages into this position, // (p==null) When the queue has been traversed and each message was executed earlier than the latest incoming message, put this message lastbreak;
                   }
                   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

The message has been queued and is waiting to be taken out for consumption — note that the Looper begins to perform;

At this point, the insertion of the message queue is complete, and according to the familiar Handler mechanism, it is time to wait for the message to be taken out of the queue for execution. But where was the message retrieved for execution? At this point, the code seems to have broken, and there is no method to call next! Don’t worry, Hanlder uses sendEmptyMessage and initiality. when we click on it, we see that it will eventually execute this constructor;

 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC)  == 0) { Log.w(TAG,"The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mlooper.mqueue; // If mQueue is assigned, Hanlder is associated with MessageQueue. mCallback = callback; mAsynchronous = async; }Copy the code

Remember that line of code I ignored in Handler so as not to break the logical coherence?

   MessageQueue queue = mQueue;
Copy the code

Where does this MessageQueue come from? We’ve already found the assignment place. In the constructor, mQueue is assigned, and it comes from Looper.

Now it’s time to get into Looper; Looper itself is a poller that pulls messages from the message queue; We know that Handler is initialized and executed on the main thread, so the code in the Handler constructor:

 mLooper = Looper.myLooper();
 mQueue = mLooper.mQueue;
 mCallback = callback;
 mAsynchronous = async;
Copy the code

We open looper.mylooper () and find only one line of code, mLooper from ThreadLocal; It was also found on sThreadLocal that the initialization was performed in prepare ()

 // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal
      
        sThreadLocal = new ThreadLocal
       
        (); . public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
       Copy the code

Let’s look at the prepare() method;

   /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

    /**
     * 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() { prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code

Ok, through these three methods, we know that prepare () and loop() methods appear synchronously, and Looper is initialized in prepare (), and the initialization of MessageQueue is also found.

For those unfamiliar with Looper and Handler, there is no initialization method called in Handler !!!!

We’ve lost track here. Let’s go!

Yeah, I’ll think of something. String it up!

The Handler itself is initialized on the main thread, and ThreadLocal can only access its information in the current thread, so initialization must be done on the main thread. So, in the main method of ActivityThread, we find the initialization and startup of Looper; PrepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper (); PrepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper () ¶ prepareMainLooper (); In the end, the initialization of Looper is indeed found in ActivityThread’s main method;

Public static void main(String[] args) {// Omit some irrelevant code //......... Looper.prepareMainLooper(); / /... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

At this point, the Looper finally starts the loop;

Hi, Dapben. Let’s see how the loop works

  
    public static void loop() {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;

        // Make sure the identity of this thread is that of the localprocess, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); // This is where we start the familiar infinite loop, fetching messages from the message queuefor(;;) {// The first step is to fetch the top Message from MessageQueue. Next () may block Message MSG = queue.next(); // might block // If the fetched message is empty, the loop endsif (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in caseA UI event set the loggerlogFinal Printer logging = me.mlogging;if(logging ! = null) { logging.println(">>>>> Dispatching to " + msg.target + "" +
                        msg.callback + ":" + msg.what);
            }

            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; MSG. Target = "Handler"; // enqueueMessage = "Handler"; // enqueueMessage = "Handler"; This is where the Hanlder's dispatchMessage method is called. At this point, all the process is finally back to the Handler. Once the distribution is complete, the loop () method code is no longer important to Hanlder. Can skip the try {directly MSG. Target. DispatchMessage (MSG); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally {if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg="+ msg.what); }}if(logging ! = null) { logging.println("<<<<< Finished to " + msg.target + "" + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident ! = newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); }}Copy the code

Now, let’s go back to Handler

We’re back, we’re back, we’re back, we’re back, we’re back, dispatchMessage(MSG);

/**
     * Handle system messages here.
     */
    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

handleMessage(msg); Did the method finally appear? That’s where we put the Handler method; To this, finally the source Handler mechanism over again!

One more comment:

In the looper.loop () method, queue.next(), which pulls messages from the Message list MessageQueue, is a blocking method, and so is blocking

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nextPollTimeoutMillis (0, -1) blocks until it is woken up. If there is no message in the queue, it will continue to block until it is woken up. This is why loop does not complete. NativeWake () will not continue until a message comes in to wake the thread. NativePollOnce (PTR, nextPollTimeoutMillis); Synchronized (this) {// final Long now = systemClock.uptimemillis (); synchronized (this) {// final Long now = systemClock.uptimemillis (); Message prevMsg = null; Message msg = mMessages;if(msg ! = null && MSG. Target == null) {// Return the current bottom of the queue and need to be retrieveddo {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {// MSG. When indicates the time for the current message to be executed, now < MSG. When indicates the time for the current message to be executed. This is what I call the bottom.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{// If this time the message should be executed immediately mBlocked =false;
                        if(prevMsg ! Next (is null) prevMsg. Next = MSG. Next; }else{// mMessages = MSG. Next; // mMessages = MSG. } msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: "+ msg); msg.markInUse(); // The retrieved message is returned. The next () method endsreturnmsg; }}elseNextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; } // Specifies whether the main thread has an exit status, mquit must be fixedfalse, will not be executedif (mQuitting) {
                    dispose();
                    returnnull; } // At this point, the normal Message is over, but Google has designed another Message type, the IdleHandler, which does not define the execution time, but will be executed when the Message is blocked. // 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 inThe future. // Here gets the number of IdleHandlers;if(pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // If there is no IdleHandler message, wait and no further executionif (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
              
     
                if(mPendingIdleHandlers == null) { 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. // If there is an IdleHandler message, Is pulled out for execution, and the executed messages are removedfor (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so wedonot run them again. pendingIdleHandlerCount = 0; NextPollTimeoutMillis = 0; // Execute IdleHandler immediately without blocking the thread. }}Copy the code

So just to sum up

ActivityThread initializes Looper. Looper initializes and creates a MessageQueue. After Looper is initialized, it calls loop() to open an infinite loop, constantly fetching messages from MessageQueue and distributing them. 3. The Handler sends the Message to the MessageQueue through the relevant method of send. The MessageQueue puts the Message into the corresponding position in the queue through the Message, waiting to be retrieved and used. 4. Looper retrieves the Message and distributes it to the corresponding Handler according to the information in the Message. At this time, Hanlder receives the Message, and we begin to process the Message and update the MAIN thread such as UI