Why Handler

Android famously does not allow UI updates in child threads. But what if we need to update the interface data after the child thread completes a time-consuming operation? At this point, we can use handlers for UI updates. Note that to update the UI we need to send a Message to MessageQueue held by the main thread, otherwise the application will still crash.

In addition to updating the UI, Handler is the Messaging mechanism of the Android system. It defines a set of rules for handling messages, broadcasting, services, and communication between threads.

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

Handler Sends messages

Handler sends messages in two ways: sendMessage and POST. After reading the source code, the post method actually calls sendMessage. Let’s take a look at the sendMessage flow. By calling sendMessage, you end up in the following method:

MessageQueue must not be empty or the program will throw an exception. Let’s look at enqueueMessage:

Two important processes are done here:

  • Msg. target = this,So the target is the Handler for the sendMessage call.(Remember the point here)
  • The enqueueMessage method of MessageQueue is called.

So far, the process has come to MessageQueue. Now look at the enqueueMessage method for MessageQueue.

Iii. Work flow of MessageQueue

Because the enqueueMessage method is quite long, we will not take a screenshot here, but look at the following code :(omit some code)

boolean enqueueMessage(Message msg, long when) {
    // 1. Target cannot be empty; otherwise, an exception will be thrown
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 2, lock, can not have more than one Handler to send messages
    synchronized (this) {
        msg.when = when;
        Message p = mMessages; // The next MSG to be queued
        boolean needWake;
        (1) the queue is empty; (2) the queue is empty;
        // (2) this MSG needs to be processed immediately, (3) it takes longer to process than the outgoing section
        // Point processing time is smaller
        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 {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            
            // select * from head; // select * from head; // select * from head;
            // Insert at an appropriate time, or at the end of the list, which is what the for loop does
            // Insert the list node
            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;
        }
        Queue.next () will sleep if MSG is not available
        if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code

The explanation is already in the above code, so let’s make a simple generalization:

  • MessageQueue is essentially a one-way linked list,The enqueue operation is locked. Multiple MSGS cannot be enqueued at the same time.
  • When the queue is inserted, the appropriate insertion location is selected according to whether the current queue is empty or the time it takes to process the message.
  • Finally, determine whether wake up is required

So far, we’ve looked at how the Handler sends the message and how the message is inserted into the linked list. How is the message handled? The loop() method of Looper is called before the message can be processed.

4. Looper’s working process

Looper’s loop() method is also quite long.

public static void loop(a) {
    // 1, get Looper object, set null processing
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    // get MessageQueue
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Call MessageQueue next() and return MSG
        Message msg = queue.next(); // might block
        if (msg == null) {
            return; }...try {
            // The target of SendMessage is set. This target is the Handler that calls SendMessage
            msg.target.dispatchMessage(msg);
        } catch (Exception exception) {
        } finally{ } msg.recycleUnchecked(); }}Copy the code

The code itself is long, but it doesn’t actually do much, so here’s a quick summary:

  • Looper.prepare() must be called before looper.loop () is called, if notLooper object will throw an exception.
  • Call MessageQueue’s next method to continuously fetch messages from the queue.
  • The MSG is then passed to the Handler’s dispatchMessage() for processing.

The source code shows that queue.next() can block, so what does this method do? Also, why call looper.prepare () in the first place, and what does it do? Let’s start with the easy ones:

The looper.prepare () creates a Looper object and implements one and only such Looper object per thread using ThreadLocal. Why create Looper? Can’t we do it without it? Let’s look at the Handler constructor:

As you can see, if Looper is empty, the program throws the exception. This myLooper() is used to get the current thread’s Looper object:

Timing dictates that we call looper.prepare () before new Handler(). Looper.prepare() is not called when the main thread uses Handler.

It turns out that the main() method of ActivityThread already does this for us:

The prepareMainLooper() internally calls looper.prepare (). So far, we have addressed Looper issues and explained why Looper must exist. There’s still one question left. What does the queue.next() method do? Why is it blocking?

Next look at MessageQueue’s next() method :(some code has been omitted)

Message next(a) {

    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if(nextPollTimeoutMillis ! =0) {
            Binder.flushPendingCommands();
        }
        // This is a native method that sleeps if messageQueue has no messages to process
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if(msg ! =null && msg.target == null) {
                // 2. The synchronization barrier looks for the next asynchronous message in the queue
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());
            }
            if(msg ! =null) {
                if (now < msg.when) {
                    // 3) the next MSG to be queued has not yet reached the time limit, and calculates the time needed to block
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // get a MSG that can be processed and return it
                    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; }}else {
                // No more messages.
                nextPollTimeoutMillis = -1; }... }}}Copy the code

The important points have already been mentioned above. Here’s a summary:

  • The process of obtaining MSG may block, and the native Native Pollonce method is invoked
  • There is a synchronization barrier when retrieving messages, that is, messages with empty target (Handler) corresponding to MSG are filtered.
  • If a MSG is available, the MSG is returned.

Fourth, look at Handler

To sort out what we know now:

  • To create a Handler, you must first create the Looper object and then call the looper.loop () method for the Handler to work.
  • Sending messages via Handler sendMessage calls queue.enqueueMessage, which is a one-way list. When calling this method, The MSG is inserted into the appropriate position based on the current queue state and when.
  • Queue.next () may sleep because the appropriate MSG is not available, and will decide if it needs to wake up when queue.enqueuemessgae is available.

This MSG is sent to the dispatchMessage Handler.

  • msg.callbackIt’s a Runnable object that we passed in via post, and we wouldn’t have gotten there if we hadn’t used POSThandleCallback(msg)In the.
  • MCallback is a CallBack object. If this parameter is not passed when we create the Handler, mCallback is null.
  • Finally, you get to the handleMessage(MSG).

Additional, because the level is limited, have wrong place unavoidable, rather misdirect others, welcome big guy to point out! Code word is not easy, thank you for your attention! 🙏