preface


Handler is a tool that is commonly used by all Android engineers. It can be used for messaging between threads, communication between components, timed tasks, and repeated tasks. This article will look at the implementation of Handler from a source code perspective.

  • 1. Create a handler

/ / frameworks/base/core/Java/android/OS/Handler Java / / creation method 1 Handler Handler = newHandler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //doSomething } }; Handler = new Handler(looper.getMainLooper ()); looper.getMainLooper ());Copy the code

The handler has several constructors in addition to the above two creation methods, but it only calls the following two methods.

/ / frameworks/base/core/Java/android/OS/Handler Java / / created above method 1 the call to end 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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Looper Looper, Callback Callback, Boolean async) {mLooper = Looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code

You can see that the constructor with loop is basically just an assignment, where loop is the existing looper passed in, and callback is an interface in Handler.

//frameworks/base/core/java/android/os/Handler.java
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
    
/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message 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);
    }
}

private static void handleCallback(Message message) {
        message.callback.run();
    }
Copy the code

If the callback is not null, the Handler will handle the callback. If the callback is not null, the Handler will handle the callback. Is empty to walk again callback handleMessage and members of the Handler itself method handleMessage, see handleCallback method, is suddenly enlighted, we often use Handler. Post (Runnable r) method. Let’s test our ideas in the source code.

/ / frameworks/base/core/Java/android/OS/Handler. The post method of Java / / Handler public final Boolean post (Runnable r) {returnsendMessageDelayed(getPostMessage(r), 0); } // Obtain the Message object m via message.obtain (), M.callback private static Message getPostMessage(Runnable r) {Message m = message.obtain (); m.callback = r;return m;
}
Copy the code

Finally, the last parameter, async, Chinese is asynchronous. Where is the value used after the assignment

//frameworks/base/core/java/android/os/Handler.java
    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

Msg.setasynchronous (true); Sets whether message is asynchronous. This is a property of Message. A Thread can have only one Looper and one MessageQueue, but it can have many handlers. If async is true when the Handler is initialized, then all messages posted by this Handler will carry the asynchronous property. MessageQueue postSyncBarrier(long when) can be used to insert a synchronous barrier into the queue. A synchronous barrier is a special message whose target=null is like a card when it is inserted. All synchronous messages after this will be stuck, and only asynchronous messages will be retrieved. You can also remove the synchronization barrier by removeSyncBarrier(int token) on the MessageQueue. The token is the value returned by the postSyncBarrier method. But both methods are currently hidden. So what you usually use is just plain messages.

Now it’s time to take a look at the more complex creation method 1.

/ / frameworks/base/core/Java/android/OS/Handler Java / / created above method 1 the call to end 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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

The first if looks for a potential leak and sees if the criteria for determining whether an anonymous inner class, a member inner class, or a local inner class are not static. It’s interesting to think about the Java feature that non-static inner classes and anonymous inner classes implicitly hold a reference to an external class, so this is the key to the potential for memory leaks. Be aware of this in your daily programming, or you may end up GG. Looper.prepare() ¶ Looper.prepare() ¶ Looper.prepare() ¶ Looper.prepare() ¶ Let’s see what looper.prepare () did.

//frameworks/base/core/java/android/os/Looper.java
    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 is 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();
        }
    }
    
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
Copy the code

You can see that a new Looper is created and then set to the sThreadLocal, where new Looper(quitAllowed) is passed with the quitAllowed parameter.

//frameworks/base/core/java/android/os/Looper.java
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
//frameworks/base/core/java/android/os/MessageQueue.java
  MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
    
    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

You can see that this argument controls whether the MessageQueue can be cleared. If the quitAllowed=true Looper’s quitSafely() method is called, all messages will be cleared and new messages will be rejected. PrepareMainLooper () ¶ If quitAllowed is false in the prepareMainLooper() method, there is no way to get the MessageQueue of the main thread to clear and refuse to insert messages, which is consistent with the Design of the Android main thread.

  • 2. Send Message handler

//frameworks/base/core/java/android/os/Handler.java
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
    
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
    
    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    public final boolean postAtFrontOfQueue(Runnable r)
    {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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);
    }
    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }
Copy the code

This is all the methods that Handler uses to send messages. Whatever method it is, it eventually comes to the enqueueMessage(MessageQueue Queue, Message MSG, long uptimeMillis) method.

//frameworks/base/core/java/android/os/Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
//frameworks/base/core/java/android/os/MessageQueue.java
    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 do not 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

You can see in the first method MSG. Target = this; The current Handler is assigned to MSG. Target, which is the key to distinguishing which Handler is handled by. The method in the Message Queue determines whether the target of the Message Queue is empty and whether the current MSG is in use. Then, a large synchronized block determines the value of mQuitting that must be entered in the Message Queue. If true, release the message and return false. The mquit value is set right before the quit(Boolean safe) value on MessageQueue, which means that once this method is called, MessageQueue will not accept any more messages. P == null; when == 0; when < p.hen; when < p.hen; when < p.hen; So anything that satisfies one of the above conditions will be inserted at the head of the message queue. The else part below is determining which part of the queue the passed MSG should be inserted into, and that’s what the for loop does. However, there is needWake, which literally means needed to be awakened, and there are two places to assign the value: needWake = mBlocked in the if block. If the current state is blocked, it needs to be awakened. NeedWake = false in the else block, if you need to wake up and p is asynchronous, notice that p is not the first message that can get here. Even if the MSG is asynchronous, it is not the first asynchronous message in the list, so there is no need to wake up.

  • 3. The processing of the Message

Looper.loop() ¶ Looper.loop() ¶ Looper.loop() ¶ Looper.loop() ¶ Looks like it’s all in here.

//frameworks/base/core/java/android/os/Looper.java
    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 local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final 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; try { 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 was not 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

Most of them are the Log, you can see a dead cycle, through the Message MSG = queue. The next (), got the MSG, to deal with the next () may cause disruption, through MSG. Target. DispatchMessage (MSG); Processing is implemented. MSG. Target is the handler that was set earlier, so it calls the handler’s dispatchMessage. This method has been analyzed above. So now it’s easy to look at MessageQueue’s next() method.

//frameworks/base/core/java/android/os/MessageQueue.java
    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(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Returniffound. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // The first placeif(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {// The second placeif (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();
                        returnmsg; }}elseNextPollTimeoutMillis = -1; // No more messages. NextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled.if (mQuitting) {
                    dispose();
                    returnnull; } // 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 = mIdleHandlers.size();
                }
                if (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.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 {
                    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 wedo not run them again.
            pendingIdleHandlerCount = 0;

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

Looking at this code, sure enough (there is no other possibility) nativePollOnce(PTR, nextPollTimeoutMillis); MSG! = MSG! = MSG! = null && MSG. Target == null if the current MSG is a synchronous splitbar, then find the MSG with asynchronous properties. The second place is to determine the time that MSG should be executed, and the dealy is to calculate the time of the delay, which cannot exceed Integer.MAX_VALUE. We get MSG without delay and return it directly. The third method states that the MSG is gone, and nextPollTimeoutMillis= -1, which means blocking when running nativePollOnce. The fourth place indicates that the quit() method is called, discards the MSG and returns null. Fifth place, look at the comment that gets the number of IDlers to run if first idle. Only if the queue is empty or the first message is in the queue (possibly a barrier) will it be processed in the future. If there is nothing to run, mBlocked is set to true and continues, then blocked. Then copy the mIdleHandlers to mPendingIdleHandlers. At the sixth place, we execute idler.queueidle (), which determines whether or not to remove the mIdleHandlers based on idler’s return value. If you don’t remove it then it will run every time it’s idle. The seventh place, assign the pendingIdleHandlerCount to 0 to avoid another execution (at most once in the next () method of the MessageQueue this time). So I’m going to set the nextPollTimeoutMillis to 0, because I don’t know if the MSG when is going to be there after all the mIdleHandlers have executed, so I’m going to set it to 0, and I’m just going to do it again, and I see that I finally understand how to do lazy loading with handlers, The secret is in the mIdleHandlers.

That’s basically the end of Handler, but that’s ok