1. Briefly describe the implementation principle of Handler

Android applications are run by messages. In Android, everything is a message, including touch events, drawing, displaying and refreshing views, and so on. Handler is the upper-layer interface of Message mechanism. In normal development, we only touch Handler and Message, and there are MessageQueue and Looper two assistants to implement Message loop system together.

(1) The Handler sends a Message via the Handler’s sendXXX or postXXX. Note that the post(Runnable r) method also wraps Runnable as a Message.

	public final boolean post(Runnable r){
    	return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean postDelayed(Runnable r, long delayMillis){
    	return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

You can see that the Runnable is assigned to message.callback. Finally, both sendXXX and postXXX will call sendMessageAtTime, and the code is as follows:

 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);
    }
Copy the code

In this method, the enqueueMessage method is finally called, and notice that this is assigned to message.target, where this is Handler.

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

The enqueueMessage method eventually calls the enqueueMessage method of MessageQueue to put the message on the queue. (2) MessageQueue MessageQueue is a priority queue. The core methods are enqueueMessage and next methods, that is, the operation of inserting messages into the queue and taking messages out of the queue. MessageQueue is a priority queue because the enqueueMessage method inserts messages according to their execution time, so that later messages are inserted later in the queue.

The next method is an infinite loop. If there is a Message in the queue, the next method removes the Message from the queue and returns the Message, or blocks if there is no Message in the queue.

(3) Looper Looper can be understood as a message pump. The core method of Looper is loop. Note that the first line of loop method will first get the Looper of the current thread through myLooper, then get the MessageQueue in Looper, and then open an infinite loop, which will continue to fetch the message through the next method of MessageQueue and execute it. The code is as follows:

public static void loop() { final Looper me = myLooper(); // Get the Looper of the current thread from the ThreadLocal. if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (;;) {// Fetch the Message from MessageQueue. Message MSG = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } / / through the message Handler distribution MSG. Target. DispatchMessage (MSG); // Recycle message MSG. RecycleUnchecked (); }}Copy the code

As you can see, after we fetch the Message, we call message. target and we call the dispatchMessage method, where target is the Handler, and it’s assigned to the Handler’s enqueueMessage. The Message is then recycled. Next, go back to Handler and look at dispatchMessage.

public void dispatchMessage(Message msg) { if (msg.callback ! = null) {// Pass a Runnable handleCallback(MSG) in the form handler.postxxx; } else { if (mCallback ! = null) {if (McAllback.handlemessage (MSG)) {return; }} // Write handleMessage(MSG) with Handler(){}; }}Copy the code

Related knowledge reference: github.com/733gh/xiong…

As you can see, we end up calling our own implementation method here. That’s the end.

2. How many handlers does a thread have? How many Looper does a thread have? How to guarantee it?

The number of handlers is independent of the thread. You can instantiate any number of handlers in a thread. There is only one Looper per thread. The constructor of Looper is declared as private. We cannot instantiate Looper using the new keyword. The only open method that can instantiate Looper is prepare. The prepare method source code is as follows:

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)); }Copy the code

When a thread calls prepare, it checks to see if it has created a Looper. If it has not, it checks to see if it has created a Looper. Looper is instantiated and stored in a ThreadLocal, and if Looper is already stored in a ThreadLocal, a RuntimeException is thrown. This means that the prepare method can be called at most once in a thread, thus ensuring the uniqueness of Looper.

3. How does the Handler thread switch?

(1) Suppose there is A thread A, Looper is started by looper. prepare and looper. loop in A thread, and A Handler is instantiated in A thread. When looper.prepare () is called, it initializes Looper and sets Looper for A ThreadLocal, which stores Looper for A thread. In addition, the MessageQueue is initialized in Looper.

(2) Then, when the loop. Loop method is called, the Loop method will get Looper in thread A through myLooper, and then get the MessageQueue in Looper, and then open an infinite Loop to wait for the execution of methods in MessageQueue. (3) At this point, another thread B is started and a Message is sent through Handler in thread B. This Message will finally be called to the equeueMessage method of MessageQueue to insert the Message into the queue through sendMessageAtTime method.

(3) As Looper’s loop is an infinite loop, when the message is inserted into the MessageQueue, the loop method will take out the message in the MessageQueue and execute the callback. In this case, Looper is the Looper of thread A, and then the Message or the Handler Callback is executed in line A. This achieves the thread switching.

4. What is the cause of Handler memory leak? How to solve it?

When using Handler, we usually instantiate the Handler using an anonymous inner class. Non-static anonymous inner classes by default hold a reference to the outer class, i.e. the anonymous inner class Handler holds the outer class. The root cause of memory leaks is that the Handler lifecycle is not consistent with the host lifecycle.

For example, an Activity instantiates a non-static anonymous inner class Handler, then sends a delayed message through the Handler, but terminates the Activity before the message is executed, because the Handler holds the Activity. This causes the Activity to fail to be collected by the GC, thus causing a memory leak.

Solution: You can declare the Handler as a static, anonymous inner class, but there is no way to call non-static methods or variables in the Activity from inside the Handler. Then the final solution can be solved using static inner classes + weak references. The code is as follows:

public class MainActivity extends AppCompatActivity {

    private MyHandler mMyHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    private void handleMessage(Message msg) {

    }

    static class MyHandler extends Handler {
        private WeakReference<Activity> mReference;

        MyHandler(Activity reference) {
            mReference = new WeakReference<>(reference);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) mReference.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

    @Override
    protected void onDestroy() {
        mMyHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}
Copy the code

Related knowledge reference: github.com/733gh/xiong…

5. Why not initialize Looper for the main thread?

A: Because the application initializes the main thread Looper during startup.

Every Java application has a main method entry, and Android is java-based no exception. The entry point for an Android application is in the ActivityThread main method:

public static void main(String[] args) { ... Looper looper.prepareMainLooper (); // Initialize the main thread Looper looper.prepareMainLooper (); . // Create an ActivityThread object. ActivityThread = new ActivityThread(); thread.attach(false, startSeq); // Get ActivityThread's Handler, which is also its inner class H if (sMainThreadHandler == null) {sMainThreadHandler = thread.gethandler (); }... Looper.loop(); // If the loop method ends and an exception is thrown, the program ends by throwing new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code

The main Looper method initializes the main Looper, creates an ActivityThread object, and then starts Looper, so that the main Looper runs when the program starts. We don’t need to initialize the main thread Looper.

6. How does Handler ensure MessageQueue concurrent access security?

A: Cyclic locking, combined with a blocking wake-up mechanism.

We can find that MessageQueue is actually a “producer-consumer” model, with Handler constantly putting messages in and Looper constantly taking them out, which involves the deadlock problem. If Looper gets the lock, but there is no message in the queue, it will wait, and the Handler needs to put the message in, but the lock is held by Looper and cannot be queued, resulting in a deadlock. The Handler mechanism is solved by looping locks. In the Next method of MessageQueue:

Message next() { ... for (;;) {... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { ... }}}Copy the code

We can see that he is waiting outside the lock. When there is no message in the queue, he will release the lock and wait until he is awakened. This will not cause deadlock problems.

Is it possible to enter a queue and hold the lock while waiting for the message to be processed because the queue is full? The difference is that MessageQueue does not have an upper limit on messages, or its upper limit is the amount of memory allocated by the JVM to the program. If the memory is exceeded, an exception will be thrown, but normally it does not.

Related knowledge reference: github.com/733gh/xiong…

7. What is the blocking wake-up mechanism for Handler?

A: Handler’s blocking wake mechanism is based on Linux’s blocking wake mechanism.

This mechanism is also similar to the handler mechanism pattern. Create a file descriptor locally, then the waiting party listens to the file descriptor, the waking party only needs to modify the file, then the waiting party will receive the file and break the wake up. Looper listens to a MessageQueue and Handler adds a message.

8. Is it possible to have a Message expedited? / What is the Handler synchronization barrier?

A: Yes/a mechanism that allows asynchronous messages to be processed faster

If a UI update action Message is sent to the main thread and there are too many messages on the Message queue, the processing of this Message will slow down, causing the interface to stall. So through the synchronization barrier, you can make the Message drawn by the UI execute faster.

What is a synchronization barrier? This “barrier” is actually a Message inserted in the header of the MessageQueue chain with a target==null. When Message joins the queue, it determines that target cannot be null. No, no, no, adding a synchronization barrier is another way:

public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; // Execute all messages if (when! = 0) { while (p ! = null && p.when <= when) { prev = p; p = p.next; } // Insert a synchronization barrier if (prev! = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; }}Copy the code

You can see that the synchronization barrier is a special target. What’s special about it? Target == NULL, we can see that it does not assign a value to the target attribute. So what does target do? Look at next method:

Message next() { ... Int nextPollTimeoutMillis = 0; for (;;) {... NativePollOnce (PTR, nextPollTimeoutMillis); Synchronized (this) {final long now = systemCloce.uptimemillis (); Message prevMsg = null; Message msg = mMessages; /** * 1 */ if (msg ! = null && MSG. Target == null) {// Sync barrier, find the next asynchronous message do {prevMsg = MSG; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) {if (now < MSG. When) {// The next message hasn't started yet, NextPollTimeoutMillis = (int) math.min (MSG. Math-now, integer.max_value); } else {// Get the message and now execute, mark MessageQueue as non-blocking mBlocked = false; /** * 2 */ // ** * 2 */ // /** ** 2 */ // // // // // // // // // // // // // // // // // // // / = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } else {nextPollTimeoutMillis = -1; } // Quit when looper.quitsafely () is called after all messages have been executed if (moper.quitsafely) {dispose(); return null; }... }... }}Copy the code

I mentioned this earlier, but let’s focus on the synchronization barrier section. Look at the code in comment 1:

if (msg ! = null && MSG. Target == null) {// Sync barrier, find the next asynchronous message do {prevMsg = MSG; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); }Copy the code

If a synchronization barrier is encountered, it will loop through the list to find the Message marked as asynchronous. IsAsynchronous returns true, and the other messages will be ignored. In this case, the asynchronous Message will be executed ahead of time. Note the code in Comment 2.

Note that the synchronization barrier is not automatically removed. You need to manually remove the barrier after it is used. Otherwise, synchronization messages cannot be processed. You can see from the source code that if the synchronization barrier is not removed, it will always be there and the synchronization message will never be executed.

If there is a synchronization barrier in MessageQueue and it is blocking, insert a new asynchronous message before all asynchronous messages. This is easy to understand, just like a synchronous message. If all synchronous messages are ignored, a new list header is inserted and the queue is blocked, at which point it needs to be woken up. Take a look at the source:

boolean enqueueMessage(Message msg, long when) { ... // Synchronized MessageQueue (this) {... if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else {/** * 1 */ needWake = mBlocked && p.target == null && MSG. IsAsynchronous (); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } needWake = false; needWake && p.asynchronous () {needWake = false; needWake && p.asynchronous (); } } msg.next = p; prev.next = msg; } // Wake the queue if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Again, this is the method I mentioned before, ignore the code that is irrelevant to the synchronization barrier and see the code at comment 1. If the inserted message is asynchronous and there is a synchronization barrier, and the MessageQueue is blocked, it needs to be woken up. If the asynchronous message is not inserted before all asynchronous messages, then no wake is required, as in note 2.

So how do we send an asynchronous message? There are two ways to do this:

  • All messages sent using asynchronous handlers are asynchronous
  • Give the Message flag async

The Handler constructor has a series of arguments of type Boolean that determine whether the Handler is asynchronous or not:

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

But the asynchronous Handler constructor is marked hide and we can’t use it, so we use asynchronous messages only by setting the asynchronous flag for Message:

public void setAsynchronous(boolean async) { if (async) { flags |= FLAG_ASYNCHRONOUS; } else { flags &= ~FLAG_ASYNCHRONOUS; }}Copy the code

But !!!! Synchronization barriers are not very useful for everyday use. Since both the synchronization barrier and asynchronous Handler methods are marked hide, Google doesn’t want us to use them. So the synchronization barrier also serves as an understanding for a more comprehensive understanding of what’s in the source code.

Related knowledge reference: github.com/733gh/xiong…