I remember in an interview many years ago, the interviewer asked the question, how do you implement thread switching in a project? He probably meant to look at the use of RxJava, but my answer was Handler and he didn’t follow up. In the wild days of early Android development, handlers did do most of the thread switching on a project, usually involving child threads updating the UI and messaging. Not only in our own applications, but also in the Android system as a whole, the Handler messaging mechanism is as important as Binder. The internal class H in ActivityThread.java is a Handler that internally defines dozens of message types to handle system events.

There is no denying the importance of Handler. Today, we will learn more about Handler through AOSP source code. The source code of the related class has been uploaded to my Github repository android_9.0.0_r45:

Handler.java

Looper.java

Message.java

MessageQueue.java

Handler

Handler is used to send and process messages stored in MessageQueue, the Message queue corresponding to the thread. Each Handler instance corresponds to a thread and the message queue for that thread. When you create a new Handler, it binds the thread that created it to the Message queue, and then it sends a Message or Runnable to the Message queue and executes it when it leaves the Message queue.

Handler has two main uses:

  1. Plan Message or Runnable execution at some point in the future
  2. Execute code on another thread

The above is translated from official notes. To put it more clearly, Handler is only provided by Android for developers to send and handle events, while MessageQueue and Looper handle the logic of how messages are stored and how messages are Looper. But to really understand the Handler messaging mechanism, a careful reading of the source code is essential.

The constructor

Handler constructors can be divided into two classes, starting with the first:

public Handler(a) {
    this(null.false);
}

public Handler(Callback callback) {
    this(callback, false);
}

public Handler(Callback callback, boolean async) {
    // If the class is anonymous, internal, or local, and the static modifier is not used, the warning may cause a memory leak
    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()); }}// Get the Looper from the current thread's ThreadLocal
    mLooper = Looper.myLooper();
    if (mLooper == null) {  Looper must be created before creating Handler. The main thread has been created automatically for us.
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // Looper holds a MessageQueue
    mCallback = callback; / / handleMessage callback
    mAsynchronous = async; // Whether to process asynchronously
}
Copy the code

Constructors of this type end up calling two-argument methods that do not pass Looper, so explicitly check to see if a Looper has been created. Looper must be created before Handler is created, otherwise an exception will be thrown directly. The Looper is created automatically in the main thread, not by hand, as you can see in the main() method of ActivityThread.java. Looper holds a MessageQueue MessageQueue and assigns it to the mQueue variable in the Handler. Callback is an interface defined as follows:

public interface Callback {
    public boolean handleMessage(Message msg);
}
Copy the code

Passing a CallBack as a constructor parameter is also an implementation of how a message is processed by the Handler.

How do we get the current thread’s Looper in the constructor above?

 mLooper = Looper.myLooper(); // Get the current thread's Looper
Copy the code

Keep that in mind for now and go back to the Looper source code and parse it in detail.

The first type of Handler constructor is simpler than the Looper parameter:

public Handler(Looper looper) {
    this(looper, null.false);
}
    
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
    
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

Just assign it.

There are several other constructors marked @hide that I won’t cover.

Send a message

The most familiar way to send a Message is sendMessage(Message MSG), but for those of you who don’t know, there’s also post(Runnable R). The method names are different, but you end up calling the same method.

sendMessage(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendEmptyMessageAtTime(int what, long uptimeMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
Copy the code

Almost all sendXXX() ends up calling the sendMessageAtTime() method.

post(Runnable r)
postAtTime(Runnable r, long uptimeMillis)
postAtTime(Runnable r, Object token, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)
postDelayed(Runnable r, Object token, long delayMillis)
Copy the code

All postXXX() methods wrap the Runnable argument as Message by calling getPostMessage() and then calling the corresponding sendXXX() method. Look at the code for getPostMessage() :

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

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

We assign the Runnable parameter to the Message callback property.

After all, sendMessageAtTime() is responsible for sending the message.

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);
}
    
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis); // Call Messagequeue's enqueueMessage() method
}
Copy the code

Handler is a function that has been handed over to MessageQueue for sending messages.

As an additional note, the uptimeMillis parameter in the enqueueMessage() method is not a timestamp in our traditional sense, but is obtained by calling systemclocke.updatemillis (), which represents the number of milliseconds since startup.

MessageQueue

enqueueMessage()

Message enqueuing is actually done by MessageQueue via the enqueueMessage() function.

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) { // MSG must have target
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) { // MSG cannot be in use
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) { // Exiting, reclaiming message and returning directly
            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.
            // Insert message queue header, need to wake up the queue
            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) { // The queue is inserted in the order when the message is triggered
                    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

It can be seen from the source code that MessageQueue is a linked list structure to store messages, messages are inserted in the order of trigger time.

The enqueueMessage() method is used to store messages, and if it does, it must fetch them, thanks to the next() method.

next()

Message next(a) {
    // 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();
        }

        // The blocking method is implemented primarily by native layer epoll listening for write events of file descriptors.
        // If nextPollTimeoutMillis = -1, the block will not timeout.
        // If nextPollTimeoutMillis = 0, it will not block and will be returned immediately.
        // If nextPollTimeoutMillis > 0, nextPollTimeoutMillis will be blocked for up to milliseconds (timeout), and will be returned immediately if any program wakes up during that time.
        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) {
                // Stalled by a barrier. Find the next asynchronous message in the queue.
                // MSG. Target == null indicates that this message is a message barrier (sent via the postSyncBarrier method)
                // If a message barrier is found, the loop finds the first asynchronous message (if any),
                // All synchronous messages will be ignored.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());
            }
            if(msg ! =null) {
                if (now < msg.when) {
                    // If the trigger time of the message is not reached, set the timeout period for the next polling
                    // 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.
                    / / get the 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(); / / tag FLAG_IN_USE
                    returnmsg; }}else {
                // No more messages.
                // No message will block until it is woken up
                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.
            // Idle Handle runs only when the queue is empty or the first message in the queue is about to be executed
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run. Loop and wait some more.
                // No idle handler needs to be run
                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.
        // The following code block is executed only on the first loop
        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 we do not run them again.
        // Set pendingIdleHandlerCount to zero to ensure it will not run 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

The next() method is an infinite loop, but blocks when there are no messages to avoid excessive CPU consumption. NextPollTimeoutMillis greater than 0 represents how long it takes to wait for the next message to block. A value of -1 indicates that no message is sent and blocks until it is woken up.

The blocking here is mainly accomplished by the native function nativePollOnce(). I don’t know the specific principle, but if you want to learn more about it, you can refer to Gityuan’s related article on Android message mechanism 2-Handler(Native layer).

MessageQueue provides methods for getting messages in and out of queues, but it does not automatically fetch messages itself. So, who gets the message out and executes it? That’s up to Looper.

Looper

Looper must be created before Handler is created. The main thread already creates Looper for us, so there is no need to manually create Looper. See activityThread.java’s main() method:

public static void main(String[] args) {... Looper.prepareMainLooper();// Create the main thread Looper. }Copy the code

prepareMainLooper()

public static void prepareMainLooper(a) {
    prepare(false);
    synchronized (Looper.class) {
        if(sMainLooper ! =null) {
            throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

SMainLooper can only be initialized once, which means prepareMainLooper() can only be called once, otherwise an exception will be thrown directly.

prepare()

public static void prepare(a) {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
    // Each thread can only execute prepare() once, otherwise an exception will be thrown
    if(sThreadLocal.get() ! =null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // store Looper to ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code

The main thread is called prepare(false), indicating that the main thread Looper is not allowed to exit. Since the main thread is constantly processing events, the system crashes once it exits. When the child thread calls prepare() to initialize Looper, the child thread is allowed to exit by default.

Each thread’s Looper is stored via ThreadLocal, keeping its thread private.

Going back to the initialization of the mLooper variable in the Handler constructor at the beginning of this article:

mLooper = Looper.myLooper();
Copy the code
public static @Nullable Looper myLooper(a) {
    return sThreadLocal.get();
}
Copy the code

Also retrieved from the current thread’s ThreadLocal.

The constructor

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); / / create a MessageQueue
    mThread = Thread.currentThread(); // The current thread
}
Copy the code

Compare this to the Handler constructor:

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

The relationship is clear.

  • LooperholdMessageQueueObject reference
  • HandlerholdLooperObject references andLooperThe object’sMessageQueueA reference to the

loop()

As you can see, the message queue is not really up and running yet. Let’s look at the standard way to write a child thread using Handler:

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

Looper.loop() is at the heart of what makes message queues work.

public static void loop(a) {
    final Looper me = myLooper(); // Get the current thread's Looper from ThreadLocal
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue; // Get the message queue of the current thread.// Omit some code

    for (;;) { // Loop out the message. It may block if there is no message
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }...// Omit some code
       

        try {
            msg.target.dispatchMessage(msg); // Message is distributed through Handler
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if(traceTag ! =0) { Trace.traceEnd(traceTag); }}...// Omit some code

        msg.recycleUnchecked(); // Put messages into a message pool for reuse}}Copy the code

Simply put, it is an endless loop to fetch messages from MessageQueue, which is distributed by Handler. After distribution, the messages are recycled into the message pool for reuse.

Fetching a message from the MessageQueue calls the messagequyue.next () method, which was analyzed earlier. It may block when there is no message, avoiding an endless loop that consumes CPU.

Message is distributed call after MSG. Target. DispatchMessage (MSG), MSG. The target is Handler object, and then to look at is how to distribute the message Handler.

public void dispatchMessage(Message msg) {
    if(msg.callback ! =null) { // Callback is of type Runnable and is sent via post
        handleCallback(msg);
    } else {
        if(mCallback ! =null) { // This branch is entered when the Handler's mCallback argument is not null
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // The Handler subclass implements the handleMessage logic}}private static void handleCallback(Message message) {
    message.callback.run();
}
Copy the code
  • If the callback attribute of Message is not empty, the Message passespostXXX()If yes, run the Runnable command.
  • Handler’s mCallback property is not empty, indicating that the constructor passed in the Callback implementationmCallback.handleMessage(msg)To process the message
  • None of the above conditions are met, only the Handler subclass is overriddenhandleMessage()Methods. This seems to be the one we use most often.

Message

I put Message at the end because I think it will be better to understand Message once I have a complete understanding of the entire messaging mechanism. First, let’s take a look at some of its important properties:

Int arg1: the value that can be carried int arg2: the value that can be carried Object obj: the content that can be carried Long when: the timeout period Handler target: Handler Runnable callback: Messages sent via POST () will have this parameterCopy the code

Message has constructors decorated with public, but it is generally not recommended to build Message directly from constructors, instead obtaining messages through message.obtain ().

obtain()

public static Message obtain(a) {
    synchronized (sPoolSync) {
        if(sPool ! =null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            returnm; }}return new Message();
}
Copy the code

SPool is a message cache pool, a linked list structure, with a maximum size of 50 MAX_POOL_SIZE. The obtain() method takes messages directly from the message pool to recycle and save resources. When the message pool is empty, a new message is created.

recycleUnchecked()

Remember when the msg.recycleunchecked () method was finally called in the looper.loop () method? This method reclaims the messages that have been distributed and processed and puts them in the cache pool.

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 = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this; sPoolSize++; }}}Copy the code

conclusion

At this point, the Handler message mechanism is all analyzed, I believe we also know the whole mechanism in mind.

  • Handlers are used to send messages, but don’t actually send them themselves. It holds a reference to the MessageQueue object through which messages are queued.
  • Handler also holds a reference to the Looper object throughLooper.loop()Method to loop message queues.
  • Looper holds the MessageQueue object application inloop()Method that calls MessageQueuenext()Method to continuously fetch messages.
  • loop()Method will eventually call HandlerdispatchMessage()Method for distribution and processing.

Finally, there has always been an interesting interview question about Handler:

Looper.loop() is an infinite loop.

It may seem like a reasonable question, but it’s not. If you think about it, does the loop() method have any direct connection to the main thread? Not really.

Recall the main() function we often write when testing code:

public static void main(a){
    System.out.println("Hello World");
}
Copy the code

Let’s just say this is the main thread, there’s no dead loop in it, it just ends, there’s no lag. But the problem is that it just ends. On the main thread of an Android app, do you want it to just end? That’s definitely not going to work. So this loop is necessary to keep the program running forever. Android is event-based, and the life cycle of even the most basic activities is triggered by events. The main thread Handler must always keep corresponding messages and events available for the program to run properly.

On the other hand, this is not a perpetual loop, and the loop() method blocks when there is no message and does not consume a lot of CPU resources.

So much for Handler. Remember that thread Looper objects are stored in ThreadLocal? The next article will look at how ThreadLocal holds thread-local variables.

This article first published wechat official account: BingxinshuaiTM, focusing on Java, Android original knowledge sharing, LeetCode problem solving.

More latest original articles, scan code to pay attention to me!