preface

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler When you started out, the big guys said that reading the source code was the fastest way to make progress.

Basic principles of Handler

An important part of Handler

  • The Message the Message
  • MessageQueue MessageQueue
  • Lopper is responsible for handling messages in MessageQueue

How are messages added to the queue

In contrast to the big logic diagram above, let’s dig a little deeper and see how a message is sent to MessageQueue and how it is processed by Lopper

The handler sends a message as shown in the following figure

And all of these methods end up executing the enqueueMessage method in Handler, so let’s see what enqueueMessage does

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    / /...
    // enqueueMessage of MessageQueue is executed
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

How does a message queue sort messages

MessageQueue sorts messages by time after receiving them

//MessageQueue
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;
        // get the message of the header
        Message p = mMessages;
        boolean needWake;
        // Compare the message in the step2 header with the current message, if the execution time of the message in the header is less than the current message
        // Execute the current message first
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            // The header message becomes the current message
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //step3 insert the current message into the middle queue
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // Sort by time
            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

🔥 2021-4-22 supplement

More of the core code can be seen here

Message prev;
// Keep looping until you find a suitable position. The right place is where you insert it
for (;;) {
    // Point the current prev to p
    prev = p;
    // Then p points to the next one he points to
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false; }}// Repoint the pointer to complete an insert
msg.next = p; 
prev.next = msg;
Copy the code

And just to illustrate, because we started the loop with prev = p and then p = p ext it’s now going to look like this.

Now I need to insert 4 into this structure. Because the time of 4 satisfies when < p. Hen is less than 5, the loop ends.

The previous structure had 2 next pointing to 5. Now a 4 is inserted.

Msg. next = p —-> 4 next points to 5.

Prev. next = MSG —->2 next points to 4.

Where is the Handler message queue created

Let’s go back to where we created this Handler, his constructor

//Handler
public Handler(a) {
    this(null.false);
}
Copy the code
//Handler
public Handler(Callback callback, boolean async) {
    / /...
    // Get the current looper
    mLooper = Looper.myLooper();
    / /...
    // Get Looper's MessageQueue
    mQueue = mLooper.mQueue;
    / /...
}
Copy the code
//Looper
final MessageQueue mQueue;

private Looper(boolean quitAllowed) {
    // A MessageQueue is created here
    mQueue = new MessageQueue(quitAllowed);
    / /...
}
Copy the code

You can see that the Handler is actually holding Looper’s MessageQueue as his own MessageQueue

What does Loope do

Messages are added to the message queue in an orderly fashion, and Looper is responsible for pulling messages out of the message queue. When executing Looper’s loop() method, Looper retrieves the message from the message queue and passes it to handler’s dispatchMessage to process the message

//Looper
public static void loop(a) {
    / /...
    for (;;) {
        // Get the message from the message queue
        Message msg = queue.next(); // might block
        / /...
            try {
                / / MSG. Traget is Handler
                // Use Handler's dispatchMessage() to process messages
                msg.target.dispatchMessage(msg);
                / /...
        } catch (Exception exception) {
            / /...
        }
        / /...}}Copy the code

A thread has several Looper

To know how many loppers there are, you must first know where Looper was created. Looper has a method for preparing

//Looper
public static void prepare(a) {
    prepare(true);
}
Copy the code

Here a new Looper is created and set to ThreadLocal

//Looper
private static void prepare(boolean quitAllowed) {
    // Check to see if there is already a looper with sThreadLocal get
    if(sThreadLocal.get() ! =null) {
        // Throw an exception if it already exists
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // If not, set a new Looper
    sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code

In ThreadLocal, looper is stored as a map to ensure that there is only one map per thread

//ThreadLocal
public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    / / get ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // If yes, bind the current ThreadLocal to Looper.
    if(map ! =null)
        Sthreadlocal.get () will not be null after set
        map.set(this, value);
    else
        If not, create a ThreadLocalMap and bind it together
        createMap(t, value);
}
Copy the code

See sThreadLocal in Looper

//Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Copy the code

It is a static final that ensures that a Looper has only one sThreadLocal

Finally, one Looper per thread is guaranteed

When does the main thread preapre

To use a Looper, you must prepare to create a Looper first. We know that the Java application entry is the Main method, but for Android, there is a main method, which is located in the ActivityThread

//ActivityThread
public static void main(String[] args) {
    / /...
    // We can see that after the program starts, the Android system helps us to set the main thread Looper prepare
    Looper.prepareMainLooper();

    / /...
    // Then help us start loop
    Looper.loop();
    / /...
}
Copy the code

Handler Memory leakage

Why does Handler cause memory leaks? We know that the inner class holds a reference to the outer class. When we do a delayed task, delay it by 10 seconds, and then exit the Activity within 10 seconds, when we sendMessage, the handler object is passed to MSG as shown in 👇 and stored in MessageQueue. Within 10 seconds, even if the Activity is destroyed, the reference relationship is still stored in the MessageQueue, so even if the Activity is destroyed, its object will not be destroyed by the GC because it will still be referenced. Memory is not collected.

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    // Here we pass the object of the handler itself to the MSG target
    msg.target = this;
   / /...
}
Copy the code

So how do you handle Handler memory leaks

1. Change Handler to a static class. The reason is that static classes do not hold references to external classes. 2. Inherit Handler and use the Activity as a weak reference. When the interface exits, the Handler’s removeMessages method is called

How does the Handler suspend when there is no message on the message queue

Looper gets the message from MessageQueue. If no message is retrieved, nextPollTimeoutMillis is set to -1, and the next loop is entered. When the nativePollOnce method is executed, If nextPollTimeoutMillis==-1 then Linux’s epoll mechanism is executed to suspend the thread and block it.

//MessageQueue
Message next(a) {
    for (;;) {
        // nextPollTimeoutMillis == -1
        // Execute the Linux epoll mechanism, the thread is in the wait state, the thread is suspended
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            / /...
            if(msg ! =null) {}else {
                // step1: nextPollTimeoutMillis becomes -1 if there is no message
                nextPollTimeoutMillis = -1; }}}}Copy the code
//Looper
public static void loop(a) {
    for (;;) {
        // Step4: Here is also a hang
        Message msg = queue.next(); // might block}}Copy the code

How to Exit Handler

Use looper to execute the quit method

handler.looper.quit()
Copy the code
//Looper
public void quit(a) {
    mQueue.quit(false);
}
public void quitSafely(a) {
    mQueue.quit(true);
}
Copy the code
//MessageQueue
void quit(boolean safe) {
    if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        //step1: Change the McOntract variable to true
        mQuitting = true;
        // Step2: Delete all messages
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        // Wake up the threadnativeWake(mPtr); }}Copy the code
//MessageQueue
Message next(a) {
    for (;;) {
        //step4: the thread is awakened. Continue to perform
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        //step5: check that the status is true and return null
        if (mQuitting) {
            dispose();
            return null; }}}Copy the code
//Looper
public static void loop(a) {
    for (;;) {
        //step6: Get message == null
        Message msg = queue.next(); // might block
        // Step7: Finally loop here 🔚
        if (msg == null) {
            return; }}}Copy the code

conclusion

Looper will first empty all messages in the message queue, and then wake up the thread using nativeWake’s native method. As we have described above, when there is no message in the message queue, the thread will be suspended and in a waiting state. After we wake up, Looper’s loop method will continue to execute. A null Message is retrieved from MessageQueue, and Looper’s loop() method is finally exited

Can the main thread Quit?

We know that the main thread is the Looper created by looper.prepareMainLooper () in ActivityThread’s main method

//Looper
@Deprecated
public static void prepareMainLooper(a) {
    // Step1: Notice that this is false
    prepare(false);
}
Copy the code
//Looper
private static void prepare(boolean quitAllowed) {
    //step2:new Looper is passed false
    sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code
//Looper
private Looper(boolean quitAllowed) {
    //step3: create MessageQueue and pass in false
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code
//MessageQueue
MessageQueue(boolean quitAllowed) {
    Step4: Change the mQuitAllowed variable to false
    mQuitAllowed = quitAllowed;
}
Copy the code
//MessageQueue
void quit(boolean safe) {
    //step5: if false, the main thread will throw an error directly
    if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit."); }}Copy the code

Looking back at Looper’s prepare method, only the main thread can create a MessageQueue that cannot quit. All other threads can create MessageQueue that can quit

//Looper
// The public method prepare is passed true
public static void prepare(a) {
    prepare(true);
}
// Private methods
private static void prepare(boolean quitAllowed) 

// The main thread passes false
public static void prepareMainLooper(a) {
    prepare(false);
}
Copy the code

Why can’t the design main thread be quit

In ActivityThread, we define an H class that inherits the Handler. This H Handler executes all major Android events, such as broadcasting, service, and Activity life cycles, so we cannot quit the main thread

//ActivityThread
class H extends Handler {}Copy the code

How does the message know which Handler sent it?

A thread can have multiple handlers, as many as new, and when we add a message to the MessageQueue, we add a target flag to which Handler sent it

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    // Step1: Here we mark which handler sent it
    msg.target = this;
    / /...
}
Copy the code
//Looper
public static void loop(a) {
    / /...
    for (;;) {
        / /...
            try {
                // Step2: This corresponds to which handler sent the message
                msg.target.dispatchMessage(msg);
                / /...
        } catch (Exception exception) {
            / /...
        }
        / /...
}
Copy the code

How does Handler ensure thread safety

//MessageQueue
boolean enqueueMessage(Message msg, long when) {
    //step1: add messages to message queue by locking
    synchronized (this) {}}Copy the code
//MessageQueue
Message next(a) {
    for (;;) {
        //step2: ensure the security of reading messages by means of shackles
        synchronized (this) {}}}Copy the code

How is Message reused

How do we clear messages from the message queue when we quit

//MessageQueue
void quit(boolean safe) {
    synchronized (this) {
        // Step1: Clear all messages
        if (safe) {
            removeAllFutureMessagesLocked();
        } else{ removeAllMessagesLocked(); }}}Copy the code
//MessageQueue
private void removeAllMessagesLocked(a) {
    Message p = mMessages;
    while(p ! =null) {
        Message n = p.next;
        //step2: Execute the message method
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
Copy the code
//Message
void recycleUnchecked(a) {
    //step3: empty all variables
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        // Default 50 messages
        if (sPoolSize < MAX_POOL_SIZE) {
            //step4: place the Message in a new linked list
            next = sPool;
            sPool = this; sPoolSize++; }}}Copy the code

Using the obtain method takes a Message from a previously empty list to use, reducing the memory consumption associated with Message creation.

//Message
public static Message obtain(a) {
    synchronized (sPoolSync) {
        if(sPool ! =null) {
            //step5: retrieve a Message from the list that has been cleared
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            returnm; }}return new Message();
}
Copy the code

This design pattern is called the Share element design pattern

Why doesn’t the main thread loop cause ANR

First of all, how does ANR appear, there are two conditions for ANR to appear, right

  • No response to input events for 5 seconds, touch feedback, etc
  • The broadcast was not completed within 10 seconds

In the main thread, an event is sent. This event takes time. Loop () of the main thread is sent to the card owner so that he can only execute the current task and cannot handle other events

The nature of ANR is caused by the inability to process messages in a timely manner and has nothing to do with its loop

Handler synchronization barrier

You might be asked in an interview, “Tell me what synchronous barriers are,” and let’s look at what synchronous barriers are

Synchronization barrier concept

The Handler synchronization barrier is used to block synchronous messages and allow asynchronous messages to pass through. Blocking synchronization messages is a synchronization barrier

When sending a message, mAsynchronous controls whether the message sent is asynchronous or not

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    // If true, the message is marked as asynchronous
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

In the Handler constructor, control is whether or not an asynchronous message is sent. But this method is hide, normally we cannot call it

//Handler
@hide
public Handler(@Nullable Callback callback, boolean async) {
    // This controls variables
    mAsynchronous = async;
}
Copy the code

Open synchronization barrier

MessageQueue provides a postSyncBarrier method to enable a synchronization barrier.

//MessageQueue
public int postSyncBarrier(a) {
    return postSyncBarrier(SystemClock.uptimeMillis());
}
Copy the code
//MessageQueue
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        //👇 note that the Messaged target is null
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if(when ! =0) {
            while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        // Returns a token that can be used to cancel synchronization barriers
        returntoken; }}Copy the code

How synchronous barriers work

Once enabled, how does a synchronization barrier transmit asynchronous messages and block synchronous messages

//MessageQueue
Message next(a) {
    / /...
    //step1:👇 if target == null is received, the synchronization barrier is open
    if(msg ! =null && msg.target == null) {
        do {
            prevMsg = msg;
            msg = msg.next;
            //step2:👇 Do a loop here to find asynchronous messages
        } while(msg ! =null && !msg.isAsynchronous());
    }
    //step3: after the asynchronous message is found
    if(msg ! =null) {
        //step4: determine if it is time to execute the asynchronous message
        if (now < msg.when) {
            // If not, wait for nextPollTimeoutMillis
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            // Remove it from the list if it is time to execute
            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; }}}Copy the code

Cancel synchronization barrier

When the synchronization barrier is removed, threads are woken up to process previously unprocessed synchronization messages.

//MessageQueue
public void removeSyncBarrier(int token) {
  
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        //step1: set synchronization barrier by token search
        while(p ! =null&& (p.target ! =null|| p.arg1 ! = token)) { prev = p; p = p.next; }if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        //step2: remove from the list
        if(prev ! =null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null|| mMessages.target ! =null;
        }
        // Step3: Clear Message
        p.recycleUnchecked();

       
        if(needWake && ! mQuitting) {// wake up the threadnativeWake(mPtr); }}}Copy the code

GIF demo

The following is a simple example to make it more intuitive, divided into three scenarios

  • No synchronization barrier is enabled, sending synchronous messages and sending asynchronous messages
  • Enable the synchronization barrier to send synchronous messages and asynchronous messages
  • Enabling and sending synchronous messages Sending asynchronous messages Disabling the synchronization barrier

No synchronization barrier is enabled, sending synchronous messages and sending asynchronous messages

As you can see, if synchronization barriers are not enabled, messages will be sent to the Handler

Enable the synchronization barrier to send synchronous messages and asynchronous messages

After the synchronization barrier is enabled, only asynchronous messages are printed instead of synchronous messages, indicating that the synchronization barrier can only allow asynchronous messages to pass through

Enabling and sending synchronous messages Sending asynchronous messages Disabling the synchronization barrier

When we remove the synchronization barrier, sync messages that we didn’t receive before will be synchronized immediately

Demo code


class HandlerAct : AppCompatActivity(a){

    companion object {
        const val TAG = "handler-tag"
        const val MESSAGE_TYPE_SYNC = 0x01
        const val MESSAGE_TYPE_ASYN = 0x02
    }


    private var index = 0

    private lateinit var handler :Handler

    private var token: Int? = null

    @RequiresApi(Build.VERSION_CODES.M)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler)
        initHandler()

        linear.addView(MaterialButton(this).apply {
            text = "Insert synchronization barrier"
            setOnClickListener {
                sendSyncBarrier()
            }
        })

        linear.addView(MaterialButton(this).apply {
            text = "Remove the barrier"
            setOnClickListener {
                removeSyncBarrier()
            }
        })


        linear.addView(MaterialButton(this).apply {
            text = "Send sync message"
            setOnClickListener {
                sendSyncMessage()
            }
        })

        linear.addView(MaterialButton(this).apply {
            text = "Send an asynchronous message"
            setOnClickListener {
                sendAsynMessage()
            }
        })

    }

    private fun initHandler(a) {
        Thread {
            Looper.prepare()
            handler = Handler(){
                when(it.what){
                    MESSAGE_TYPE_SYNC -> {
                        Log.i(TAG, <========== index:${it.arg1}")
                    }
                    MESSAGE_TYPE_ASYN -> {
                        Log.i(TAG, <========== index:${it.arg1}")}}true
            }
            Looper.loop()
        }.start()
    }

    private fun sendSyncMessage(a) {
        index++
        Log.i(TAG, "Insert sync message ==========> index:$index")
        val message = Message.obtain()
        message.what = MESSAGE_TYPE_SYNC
        message.arg1 = index
        handler.sendMessageDelayed(message, 1000)}// Inserts asynchronous messages to the message queue
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    private fun sendAsynMessage(a) {
        index++
        Log.i(TAG, "Insert asynchronous message ==========> index:$index")
        val message = Message.obtain()
        message.what = MESSAGE_TYPE_ASYN
        message.arg1 = index
        message.isAsynchronous = true
        handler.sendMessageDelayed(message, 1000)}@RequiresApi(Build.VERSION_CODES.M)
    @SuppressLint("DiscouragedPrivateApi")
    fun sendSyncBarrier(a) {
        try {
            Log.d(TAG, "Insert synchronization barrier")
            val queue: MessageQueue = handler.looper.queue
            val method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier")
            method.isAccessible = true
            token = method.invoke(queue) as Int
            Log.d(TAG, "token:$token")}catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // Remove the barrier
    @SuppressLint("DiscouragedPrivateApi")
    @RequiresApi(api = Build.VERSION_CODES.M)
    fun removeSyncBarrier(a) {
        Log.i(TAG, "Remove the barrier")
        try {
            val queue: MessageQueue = handler.looper.queue
            val method: Method = MessageQueue::class.java.getDeclaredMethod(
                "removeSyncBarrier",
                Int::class.javaPrimitiveType
            )
            method.isAccessible = true
            method.invoke(queue, token)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

}
Copy the code