Deep Understanding of Handler Message Mechanisms

Time:2019-10-6

I remember in an interview many years ago, the interviewer asked such a question.How do you usually switch threads in a project?His intention should be to examine the use of RxJava, but my answer isHandlerHe did not ask any more questions. In the wilderness of early Android development, Handler did take on most of the threading switching tasks in the project, usually including sub-threads updating UI and messaging. Not only in our own applications, but also in the whole Android system, Handler message mechanism is extremely important, no less than the status of Binder.ActivityThread.javaInternal classes inHIt’s a handler, which internally defines dozens of message types to handle some system events.

There is no doubt about the importance of Handler. Today we will learn more about Handler through AOSP source code. Source code including annotations for related classes have been uploaded to my Github repository android_9.0.0_r45:

Handler.java

Looper.java

Message.java

MessageQueue.java

Handler

HandlerMessage queues for sending and processing threadsMessageQueueStored inMessage。 Each Handler instance corresponds to a thread and its message queue. When you create a new Handler, it binds the thread that created it to the message queue, and then it sends it to the message queue.MessageperhapsRunnableAnd execute when they leave the message queue.

Handler has two main uses:

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

The above translation is from the official annotations. To put it bluntly, Handler is just what Android provides developers with to send and process events, and the logic of how messages are stored and how messages are recycled is handed over to them.MessageQueueandLooperTo deal with it, users don’t need to care. But to really understand the Handler message mechanism, it is essential to read the source code carefully.

Constructor

Handler’s constructors can be roughly divided into two categories. First, let’s look at the first category:

public Handler() {
    this(null, false);
}

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

public Handler(Callback callback, boolean async) {
    // If it is an anonymous class, an internal class, a local class, and does not use a static modifier, the prompt may lead to memory leaks
    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 Looper from ThreadLocal of the current thread
    mLooper = Looper.myLooper();
    If (mLooper == null) {// Make sure to create Looper before creating Handler. The main thread has been created for us automatically.
        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; // asynchronous processing or not
}

This kind of constructor calls the method of two parameters eventually, and the parameters are not passed.LooperSo check explicitly whether a Looper has been created. You must create Looper before creating Handler, otherwise you will throw an exception directly. Looper has been created automatically in the main thread without us having to create it manually.ActivityThread.javaOfmain()As you can see in the method. Looper holds a message queueMessageQueueAnd assign it to HandlermQueueVariables.CallbackIt is an interface defined as follows:

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

Importing CallBack through constructor parameters is also an implementation of Handler’s message processing.

Let’s look back at how to get the looper of the current thread in the above constructor.

MLooper = Looper. myLooper (); // Gets the Looper of the current thread

Here’s to remember that when you look back at the Looper source code, you’ll parse it in detail.

Looking at the first constructor of Handler, the second constructor is actually very simple, but a lot more.LooperThe parameters are only:

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;
}

Direct assignment is enough.

In addition, there are several tags for@hideThe constructor is not explained.

send message

The most familiar way to send messages issendMessage(Message msg)Well, maybe some people don’t know that there is one.post(Runnable r)Method. Although the method name is different, the last call is the same method.

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

Almost all of themsendXXX()All the last calls aresendMessageAtTime()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)

Be-allpostXXX()Method callsgetPostMessage()Wrap the Runnable in the parameter as Message and call the correspondingsendXXX()Method. To glance atgetPostMessage()Code:

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;
}

Mainly assigning Runnable in the parameter to MessagecallbackAttribute.

The task of sending messages eventually falls to the same end.sendMessageAtTime()On the body.

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 the enqueueMessage () method of Messagequeue
}

Handler is a cashier, and the task of sending messages is handed over again.MessageQueueTo deal with.

One more point,enqueueMessage()Parameters in the methoduptimeMillisIt’s not our traditional timestamp, it’s a call.SystemClock.updateMillis()Gets, which represents the number of milliseconds since the boot.

MessageQueue

enqueueMessage()

Message queuing is actually done by MessageQueueenqueueMessage()Function to complete.

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 used
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        If (mQuitting) {// Exiting, Recycling messages and returning them 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 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) {// Insert queues in the order of triggering time of messages
                    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;
}

As can be seen from the source code, MessageQueue stores messages in a linked list structure, and messages are inserted in the order of trigger time.

The enqueueMessage () method is used to store messages. Now that it is stored, it must be taken, depending onnext()Method.

next()

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();
        }

        // Blocking method is mainly realized by the epoll of native layer listening for the write events of file descriptors.
        // If nextPoll Timeout Millis = 1, a constant blocking does not timeout.
        // If nextPollTimeoutMillis = 0, it will not block, return immediately.
        // If nextPoll Timeout Millis > 0, the longest blocking nextPoll Timeout Millis milliseconds (timeout) will be returned immediately if a 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 the message is a message barrier (sent through the postSyncBarrier method)
                // If a message barrier is found, the first asynchronous message (if any) is looped out.
                // All synchronized messages will be ignored (usually synchronized messages are sent)
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Set the timeout for the next poll before the message trigger time arrives
                    // 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 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 (); // marking FLAG_IN_USE
                    return msg;
                }
            } else {
                // No more messages.
                // No news, it will be blocked until it is awakened.
                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 if the queue is empty or the first message in the queue is about to execute
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                // There is no idle handler to run and continue the loop
                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.
        // Only the first loop executes the following block of code
        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.
        // Zero pending IdleHandlerCount to ensure that it no longer runs
        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;
    }
}

next()The method is a dead loop, but it blocks when there is no message, avoiding excessive CPU consumption.nextPollTimeoutMillisMore than 0 indicates the time needed to block waiting for the next message. When it equals – 1, it means there is no news, and it is blocked until it is awakened.

The blocking here depends mainly on the native function.nativePollOnce()To finish. I don’t know how it works. Students who want to learn more can refer to Gityuan’s article Android Message Mechanism 2-Handler (Native Layer).

MessageQueue provides a way for messages to enter and leave the queue, but it does not automatically cancel messages itself. So who’s going to take the message out and execute it? That depends.LooperNow.

Looper

Before creating Handler, we must create Looper, and the main thread has created Looper automatically for us, so we don’t need to create it manually anymore. SeeActivityThread.javaOfmain()Method:

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

prepareMainLooper()

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

sMainLooperIt can only be initialized once, that is to sayprepareMainLooper()You can only call it once, otherwise you will throw an exception directly.

prepare()

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

private static void prepare(boolean quitAllowed) {
    // Each thread can only perform prepare () once, otherwise an exception will be thrown directly.
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //Store looper in ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

What is called in the main thread isprepare(false)That means the main thread Looper is not allowed to exit. Because the main thread needs to deal with various events continuously, the system will be paralyzed once it withdraws. And we call it in sub-threadsprepare()To initialize Looper, the default call isprepare(true)Subthread Looper is allowed to exit.

The Looper for each thread is throughThreadLocalTo store and keep its threads private.

Back to the Andler constructor introduced at the beginning of this articlemLooperInitialization of variables:

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

Also through the current threadThreadLocalTo get it.

Constructor

private Looper(boolean quitAllowed) {
    MQueue = new MessageQueue (quitAllowed); //Create MessageQueue
    MThread = Thread. current Thread (); // current thread
}

Contrast Handler’s constructor again:

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

The relationship is clear.

  • LooperholdMessageQueueObject reference
  • HandlerholdLooperObject references andLooperObjectMessageQueueQuotation

loop()

Seeing this, the message queue is not really working yet. Let’s first look at a standard way of writing a sub-thread using Handler:

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();
    }
}

The core of turning the message queue around isLooper.loop()

public static void loop() {
    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 for the current thread

   ...// omit part of the code

    For (;;) {// Loop to fetch messages, which may be blocked when no messages are available
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...// omit part of the code
       

        try {
            Msg. target. dispatchMessage (msg); // Distributing Messages through Handler
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        ...// omit part of the code

        Msg. recycleUnchecked (); /// Put messages into message pools for reuse
    }
}

Simply put, a dead loop cancels messages continuously from MessageQueue, and the messages are distributed through Handler. After distribution, the messages are recycled into the message pool for reuse.

What cancels the message call from the message queue isMessageQueue.next()The method has been analyzed before. Blocking may occur when there is no message to avoid the CPU consumption of the dead loop.

The distribution call after the message is taken out ismsg.target.dispatchMessage(msg)msg.targetIt’s a Handler object. Finally, let’s look at how Handler distributes messages.

public void dispatchMessage(Message msg) {
    If (msg. callback!= null) {// callback is of Runnable type, sent by post method
        handleCallback(msg);
    } else {
        If (mCallback!= null) {// Handler's mCallback parameter is not space-time, enter this branch
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        HandleMessage (msg); handleMessage logic implemented by // Handler subclass
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
  • Message callback property is not empty, indicating that the message is throughpostXXX()To send, simply execute Runnable.
  • The mCallback property of Handler is not empty, indicating that the Callback implementation is passed into the constructor and called.mCallback.handleMessage(msg)To process messages
  • None of the above conditions are satisfied. It is only possible that the Handler subclass has been rewritten.handleMessage()Method. It seems to be one of the most commonly used forms.

Message

The reason whyMessageIn the end, because I think it will be more profound to know Message after I have a thorough understanding of the whole message mechanism. First, let’s look at some of its important attributes:

Int what: message identification
Int arg1: Portable int value
Int arg2: Portable int value
Object obj: Portable content
Long when: Overtime
Handler target: Handler for processing messages
Runnable callback: Messages sent through post () have this parameter

Message yes.publicModified constructors, but it is generally not recommended to construct messages directly through constructors, but throughMessage.obtain()To get information.

obtain()

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

sPoolIs the message buffer pool, linked list structure, its maximum capacityMAX_POOL_SIZEIt is 50.obtain()The method will cancel the information directly from the message pool, recycle and save resources. When the message pool is empty, create new messages.

recycleUnchecked()

RememberLooper.loop()The method is finally calledmsg.recycleUnchecked()Method? This method reclaims the messages that have been distributed and processed and puts them into the cache pool.

void recycleUnchecked() {
    // 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++;
        }
    }
}

summary

At this point, the Handler message mechanism is all analyzed, I believe you are also aware of the whole mechanism.

  • Handler is used to send messages, but not to send them by itself. It holds references to MessageQueue objects and queues messages through MessageQueue.
  • Handler also holds references to Looper objects throughLooper.loop()Method lets the message queue loop.
  • Looper holds MessageQueue object applications inloop()Method will call MessageQueue’snext()The method is to cancel the interest ceaselessly.
  • loop()The message retrieved from the method will eventually call Handler’sdispatchMessage()Method for distribution and processing.

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

Looper.loop()It’s a dead cycle. Why doesn’t it kill the main thread?

It seems reasonable to ask, but it is not. Think about it carefully. Is there any direct correlation between the dead loop () method and the blocked main thread? Not really.

Recall that we often write when testing codemain()Function:

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

Let’s just think of it as the main thread. There is no dead loop in it, and the execution ends directly without any cards. But the problem is that it’s over. On the main thread of an Android application, do you want it to end directly? That must not work. So this dead cycle is necessary to ensure that the program can run continuously. Android is based on an event system, including the most basic Active life cycle triggered by events. The main thread Handler must always keep corresponding messages and events in order for the program to run properly.

On the other hand, this is not a dead loop that is circulating all the time. When there is no message, the loop () method blockages and does not consume a lot of CPU resources.

That’s all about Handler. Remember that the article said that the Looper object of a thread is saved inThreadLocalIs it in China? Let’s talk about the next article.ThreadLocalHow to saveThread local variables.

The article’s first Wechat Public Number:Theory of mindFocus on Java, Android original knowledge sharing, LeetCode problem solving.

More latest original articles, pay attention to me!

Deep Understanding of Handler Message Mechanisms

Recommended Today

PHP realizes UnionPay business H5 payment

UnionPay business H5 payment interface document: document address 1: H5 payment interface address: 1: Alipay payment Test address: http://58.247.0.18:29015/v1/netpay/trade/h5-pay Official address: https://api-mop.chinaums.com/ 2: UnionPay payment Test address: http://58.247.0.18:29015/v1/netpay/uac/order Official address: https://api-mop.chinaums.com/ 2: Basic parameters required by the interface The interface uses get parameters. After the interface parameters are directly put into the interface address, the […]