preface

Handler can be said to be used a lot by friends. It can be said that Handler is the basis for supporting the operation of the entire Android system. Essentially, Android system is event-driven. At the heart of handling events is the Handler. We’ll go from simple usage to source code analysis to give you a thorough understanding of the nature of Handler. No more confusing questions like why Looper. Loop doesn’t block the main thread, how Handler switches threads, etc.

Simple to use

You typically implement a Handler in the main thread and use it in child threads.

class HandlerActivity: AppCompatActivity() {

    private val mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        // Send messages through custom handlers in child threads
        thread {
            mHandler.sendEmptyMessageDelayed(1.1000)}}// Create a custom Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Main thread: handleMessage:${msg.what}")}}}Copy the code

Or sometimes you need to create handlers in child threads that run in the main thread

class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        thread {
            // Get main looper running on the main threadmHandler = MyHandler(Looper.getMainLooper()) mHandler!! .sendEmptyMessageDelayed(1.1000)}}// Create a custom Handler
    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Child thread: handleMessage:${msg.what}")}}}Copy the code

These are two common expressions. Notice that in the second usage looper.getMainLooper () is used as an argument, even though MyHandler is defined in the child thread, its handleMessage method still runs in the main thread. Let’s see what this parameter is

public Handler(@NonNull Looper looper) {
       this(looper, null.false);
}
Copy the code

You can see that the Looper is the looper.getMainLooper () argument we passed in above, which tells you which thread the handleMessage method is run in depends on the Looper. So who is this Looper, and how does it do thread switching?

An overview of the

Let’s start with a picture

This is how the entire Handler flows through the Java layer. As you can see, the Message object is added to the MessageQueue after the Handler calls the sendMessage method. And this MessageQueue is wrapped in Looper. So what does a Looper object do? What does it have to do with Handler? Let’s take a look at their specific responsibilities

  • The Handle messaging mechanism, as an exposed tool, contains a Looper. Responsible for Message sending and processing

    • Handler.sendMessage(): To the message queuesendVarious message events
    • Handler.handleMessage():To deal withCorresponding message event
  • As the core of the message loop, Looper contains a MessageQueue MessageQueue, which is used to record all messages to be processed. Looper.loop() continuously extracts messages from MessageQueue and distributes them to the target handler according to the distribution mechanism, which can be thought of as a Message pump. Note that this is where the thread switch is done.

  • MessageQueue, as a Message queue, contains a series of linked messages. Don’t be fooled by the name of this Queue; it is a Queue, but the list of messages is maintained internally by a single linked list data structure, waiting for Looper to extract.

  • Message is the body of the Message, which in turn contains a target Handler, the target that will ultimately process it

Oh? So that’s what they do, but I still don’t understand how they work. Just like you told me that doctors are responsible for curing diseases and police are responsible for catching bad people, how do they do it?

Handler

Start with the sendMessage method we’re all most familiar with. The sendMessage method means exactly what it means to send a message, but where to go? Here’s the code:

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
}
Copy the code

The sendMessageDelayed method is called:

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code

Then call the sendMessagAtTime method:

public boolean sendMessageAtTime(@NonNull 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

The sharp-eyed kid will notice, wait no, there’s a traitor in this code, no, there’s a weird thing. Yes, it is the MessageQueue shown in the flow chart just now. The MessageQueue actually exists and is passed along as a parameter to the enqueueMessage method. No matter how you send a message using Handler, the result is going to be the enqueueMessage method.

Here is the call chain for the method:

You can see that you end up in the enqueueMessage method anyway. What exactly does this enqueueMessage method do?

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

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

EnqueueMessage does two things: assigning a value to Message and calling the enqueueMessage method of the MessageQueue passed in. Notice that the last enqueueMessage method is in MessageQueue, it’s no longer a Handler method, that is, the call goes here. The flow of events is no longer controlled by Handler.

Handler::enqueueMessage first line MSG. Target = this; What is this? This is the handler itself in the handler method, so this line of code assigns the handler itself to the target field of the Message object. We can look at the following definition of the target field:

// The simplified code
public final class Message implements Parcelable{
    @UnsupportedAppUsage
    /*package*/ Handler target;
}
Copy the code

Every sent Message holds a reference to the Handler that sent it.

That’s right. Every sent Message contains a reference to the Handler that sent it. After all, the Handler sends it out, so it doesn’t have to know who did it so it can retaliate. The msg.setasynchronous (true) line sets the asynchronous message and will be ignored for the moment. Let’s look at queue.enqueuemessage (MSG, uptimeMillis). From this line of code, Message says goodbye to Handler.

MessageQueue

MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue You may have got the Google engineers wrong, but there’s nothing wrong with the name. Mechanically, it does look like a queue. What kind of feature is a queue? First in, first out. This order is divided by time, the first time is in front of the time is behind. And that’s exactly what happens in this single linked list, in chronological order. I’m not going to talk about that, but we’ll talk about that in a moment when we talk about how to implement message delay.

At this point you may be wondering what MessageQueue is and where it came from. As you may recall, there was this line in the sendMessageAtTime method above:

MessageQueue queue = mQueue;
Copy the code

So where does this mQueue get assigned? In the constructor, of course

public Handler(@Nullable Callback callback, boolean async) {
    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());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

No, you’re kidding me. In the beginning, you inherited a Handler that didn’t have these parameters. Oh, boy, don’t worry, you see this parameterless constructor also calls this method.

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

In the constructor with arguments, you can see two lines:

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
       "Can't create handler inside thread " + Thread.currentThread()
         + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
Copy the code

This is where the mQueue we use in the Handler is assigned. The assignment is not simple, it takes the MessageQueue of the other Looper as its own MessageQueue, ** And there is a key point in the above code, which is to call the Looper. MyLooper () method to retrieve the Looper object. If it is empty, an exception is thrown. ** This is a very important point, so let’s make a notation and we’ll come back to this line of code. You’ll see what it does.

Leaving Looper for now, let’s move on to our MessageQueue. MessageQueue queue.enqueuemessage (MSG, uptimeMillis) is used to send messages. Now that we’ve got our queue, let’s go in and see what this method does.

// MessageQueue.java
// Omit some code
boolean enqueueMessage(Message msg, long when) {

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        // [1] get the head of the queue
        Message p = mMessages;
        boolean needWake;
        
        // [2] If the message does not need delay, or the execution time of the message is earlier than the header message, it is inserted into the queue head
        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 {
            [3] The message is inserted into the middle of the queue
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            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;
        }

        if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code

There are three main steps (see code annotation above).

  1. mMessagesIs the first message in the queue, fetch it
  2. Check whether the message queue is empty, if yes, put the current message to the queue head; If the current message does not need to be delayed, or the execution time of the current message is earlier than the header message, it is also put to the queue head.
  3. If this is not the case, the current queue is not empty and the header message is executed earlier than the current message and needs to be inserted into the middle of the queue.

How do you determine this position? It is still the time that the message is executed.

By traversing the queue, a message is inserted in front of a message in the queue when its execution time is later than the current message.

As you can see, a message queue is a one-way linked list connected by the order of execution of the messages.

To get an executable message, you simply walk through the list and compare the current time to the message’s execution time to see if the message needs to be executed.

Well, that concludes the analysis of MessageQueue at the Java layer.

Wait, this is over?

Yes, at this point, messages have been added to a queue named queue, which is actually a singly linked list.

No, how does my handleMessage method get called? Just add the message and you’re done? What about thread switching?

This is really the end, at least in the Java layer. The message to this step is added to the queue, and the Handler and MessageQueue have done their work during the sending process. But since there are queues, it is impossible to say that light adds and does not read. Why else would I add it?

Yes, it’s time for Looper to hit the big time.

Looper

So, as I mentioned above, the MessageQueue object in this Handler is actually the Looper of this Handler’s MessageQueue, and the Handler adds a message to the MessageQueue, This is essentially adding objects to the MessageQueue held by Handler’s Looper. This may be a little convoluted, but it’s important to understand that this MessageQueue is Looper’s, not Handler’s. Knowing this will help you understand what’s going on.

You might say, “Where did this Looper come from? I never saw it when I created this Handler.” Yes, it doesn’t appear when you use Handler, but remember that constructor for two parameters in Handler? Here it is:

//Handler.java
// Omit some code
public Handler(@Nullable Callback callback, boolean async) {
	// Tap the blackboard, underline the sentence !!!!
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

The Looper object is retrieved by the Handler using the looper. myLooper method. However, if you don’t get it, you will have to throw exceptions.

As mentioned in the responsibility analysis, the Looper object acts as the core of the message loop, continually pulling messages out of its MessageQueue and distributing them.

Is it ok to speak human language?

Just now, I said that there were so many messages in the MessageQueue queue that no one took them. Looper, the boss of MessageQueue, could not bear to watch them and said that it was too wasteful of you. Let me take them one by one, and then it was responsible for taking them one by one, and then it would see who sent them and let them deal with them.

So let’s see what the looper.mylooper () method does. How does it return a Looper object?

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

What is sThreadLocal? Let’s look at the definition

//sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Copy the code

You can see that sThreadLocal is a ThreadLocal class and its generic is a Looper object. ThreadLocal provides a ThreadLocal variable that can be manipulated by each thread using set() and get() without interfering with other ThreadLocal variables. In a nutshell: The variable that fills ThreadLocal belongs to the current thread and is isolated from other threads.

Yikes, there is also a line comment in the source code that says sthreadlocal.get () will return null unless you have called prepare (). ThreadLocal’s get method must have data to return. When was the data inserted? The value is valid only if I call prepare(). So let’s see what this method does.

public static void prepare(a) {
        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

The prepare(Boolean quitAllowed) method is used to prepare(Boolean quitAllowed). This method throws an exception if sThreadLocal has a value. The prepare method must be called only once. If the prepare method is not called, it has no value. The argument constructor in this Handler has a line that raises an exception:

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
       "Can't create handler inside thread " + Thread.currentThread()
         + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
Copy the code

If Looper is empty then throw exception, now we know, when Looper is empty? This method is null if the prepare method is not called, meaning that a Looper object must be created before a Handler can be constructed. It’s very important, so I underlined it and I underlined it in bold, so remember it. This will be used later when you customize a Looper.

Sthreadlocal. set(new Looper(quitAllowed)); What did it do? Did it get in like Jose

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
}
Copy the code

The set method first fetches the current thread and then retrieves a map. This map stores content as key-value pairs. If the obtained map is empty, a map is created. Insert the value if it is not empty. Note that the key is the current thread and the value is Looper. That is, threads and Looper correspond one to one. A Looper, as many people call it, is tied to a thread, in the form of a key-value pair stored in a map. Nothing fancy. You can do it if you want.

We also need to look at the Looper constructor:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
Copy the code

In the Looper constructor, you can see that it creates a MessageQueue, the same MessageQueue that the Handler shamelessly uses. It is important to note that the prepare method must be called only once, after which a Looper object is created. This means that only one Looper object is created in a thread, and only one MessageQueue object is created in a Looper object.

Now let’s go through the process

Create a MessageQueue for the Looper object. Create a MessageQueue for the Looper object. Create a MessageQueue for the Looper object When a message is sent, the message to be sent is given to MessageQueue and added to the queue. Does it feel like something is missing? That’s right, it seems Looper’s role is not reflected in this one. What about the distribution of messages? And you said you had to call prepare() to create Looper, which I never did. So who created this Looper?

As mentioned earlier, Looper is created as a value and inserted into a map, which is a ThreadLocal. The key is the thread on which the Looper was created. So called Looper and thread binding. We usually never create a Looper when we use it, but we know that the callback handleMessage method in Handle runs on the main thread. Looper’s job is to distribute messages, which means Looper objects distribute messages to handlers in the main thread. When we create a Looper, the thread on which the Looper resides is the main thread. In other words, the thread bound to the Looper is the main thread.

Got it. I’ll check in with the interviewers.

And since it’s the main thread, you should know, who created the main thread? ActivityThread class. The ActivityThread class is also the entry point to the entire app. I used to wonder, since Android is written in Java, shouldn’t Java have a main method? Why didn’t I write Android with this main method? Actually, there’s this main method in ActivityThread, which is the entry point to your application, so when you click on your app, you’re going to go to this main method, and you’re going to do a bunch of things that I’m not going to analyze here. All you need to know is that this main method is the real entry point.

Let’s see what the main method does:

//ActivityThread.java
// Omit some code
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        Process.setArgV0("<pre-initialized>");
    	//1 Tap the blackboard, underline, that's it!
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    	//2 Knock on the blackboard, underline, this sentence!
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

Is this code very consistent with our usual Java program? The familiar main method is back. As you can see from the main method, it calls Looper’s prepareMainLooper method:

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

As you can see from comment 1, this method finally calls the prepare method of Looper. Create a Looper and insert it into the map along with the current thread. Which thread is the current thread? The main thread!

The Looper is automatically created in the entry method at APP startup, and it also automatically binds us to the main thread. That is, the Looper in the Handler we normally use is the Looper created in the main thread. If you’re observant, you’ll notice that you can’t call the prepareMainLooper method. Why? SMainLooper is no longer Null because this method is executed once on the entry. If you call it again, you will throw an exception

Now Looper has it, and Looper’s MessageQueue has it. Is it time to distribute the message? I Handler sent messages but it has been a long time, you here analysis of a chase, I still do not work?

Okay, let’s start with a hypothetical scenario.

You buy a package and you know it will arrive sooner or later, but you’re not sure exactly when it will arrive. What should you do if you want to get the package early?

You keep asking the delivery company, where is my delivery? Where is it? Of course, in reality, you usually wait for the Courier to call before you pick up the package. The problem is, that’s the procedure.

Looper may want to distribute messages, but it doesn’t know when you’re going to send them, so it just starts an endless loop of trying to get data from the queue. Where does this cycle begin? That’s right, in comment 2, looper.loop () starts an endless loop and keeps trying to fetch messages from the queue.

// Looper.java
public static void loop(a) {

    // Get the current thread's Looper
    final Looper me = myLooper();
    
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    // Get the message queue of Looper
    final MessageQueue queue = me.mQueue;

    // omit some code...
    //1 this is an infinite loop
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        try {
            msg.target.dispatchMessage(msg);
            // omit some code...
        } catch (Exception exception) {
            // omit some code...
            throw exception;
        } finally {
            // omit some code...
        }
        // omit some code...msg.recycleUnchecked(); }}Copy the code

When Looper gets the Message object, it calls the dispatchMessage method for the Target field of the Message. Yes, it’s the Handler that sent it. The message is already memorized by the Handler when it’s sent. Now it’s his turn to get revenge

We can follow up by looking at the dispatchMessage method:

//Handler.java
public void dispatchMessage(@NonNull Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code

As you can see, the message is first sent to Meesgae’s callback, which we did not define, so there is also an mCallback. This mCallback is created with the option of passing a CallBack, which still contains the handleMessage method. That is, you can customize a class that inherits the Handler and overrides the handleMessage method, or you can simply new a Handler and pass a callback. Of course, this is all very simple, I won’t go over it, you can try it yourself.

When Looper gets the Message and finds the Handler that sent the Message based on the Message target field, it calls Handler’s handleMessage method. Which thread is this Looper running on? The main thread, which thread does it run on? Still the main thread! In which thread is the handleMessage method running at this time? Still the main thread! Unconsciously, the thread has been switched over, isn’t it amazing? It’s not magic, it’s just that Looper in the main thread keeps trying to call the handleMessage method, and if it gets a message, it succeeds, and the handleMessage method is called from the main thread. Looper doesn’t care which thread the handler is on, I’m only calling your handleMessage method on the main thread anyway. This is the essence of thread switching. There is no thread switching, the main thread Looper is constantly trying to call.

Some friends may have been confused, let’s comb again from beginning to end ha ~

  1. mainThreadIn theActivityThreadWe first create one that runs on the main threadLooperAnd bind it to the main thread.
  2. LooperI create another oneMessageQueueAnd then callLooper.loopMethod is constantly trying to fetch from the main threadMessage
  3. LooperIf you get itMessage, then call send this in the main threadMessagetheHandlerthehandleMessageMethods.
  4. We pass through the main thread or child threadLooper.getMainLooperOne was created for the parameterHandler.
  5. Sent in the child threadMessage, in the main threadLooperAnd then it went on and on and onMessage, called this in the main threadHandlerthehandleMessageMethods.

It is important to note that the Looper object fetched from the looper. loop method is not necessarily the main thread, because it is the Looper object fetched from the current thread. Except in ActivityThread, it’s the main thread, so we get the Looper object for the main thread. So if we want to create a Looper in a child thread, we’ll do that in a moment.

Here may be some partners or muddled, I still do not quite understand how to switch the thread. Let’s use a metaphor to explain it

First there is a pupil Xiao Ming, Xiao Ming’s task is to write homework. Then there is a teacher. The teacher’s job is to correct the homework. The class also has a study commissary, whose job is to collect homework and hand it in to the teacher for correction.

Under normal circumstances, the teacher is the school has hired good, we do not need to hire their own teachers. Teachers generally only grade homework in the office, which we can understand as the main thread. The school is our app. Teachers are Looper objects. Xiaoming’s classmate is Handler, the commissary of study is MessageQueue, and the homework is Message. Looper was in the office and kept getting the Message of the next homework to be marked from MessageQueue. All of a sudden, the teacher found there were mistakes in the homework. The teacher was very angry. So the teacher called Xiaoming (main thread) to the office and asked Xiaoming (main thread) to correct the homework (handleMessage). In this example, Xiao Ming is the Handler, he can write the homework (sendMessage) anywhere, that is, he can write the homework at home, he can write the homework in the classroom, he can write the homework in the small park, where each place is different threads. But after finishing the homework, you must hand it to the learning committee. The learning committee has a pile of homework in hand, which is the Message queue, and the learning committee is MessageQueue, who is responsible for collecting homework, that is, collecting Message. The teacher correcting homework in the office, found mistakes, called Xiaoming to the office, let Xiaoming correct mistakes in the office. Here, the office is the main line, the teacher does not care where Xiaoming is writing homework, the teacher is only concerned about homework mistakes, xiaoming needs to correct mistakes in the office. This is the handleMessage method running on the main thread. But there is also a problem, can not say that you xiaoming in the office to correct the wrong to change the endless, that does not affect the back of the students homework correction? If Xiao Ming really corrected the mistakes endlessly, that is, in the main thread to do time-consuming operation for a long time, then the teacher can not carry out the next classmate’s homework correction, a long time, the teaching can not be carried out. This is known as the ANR problem. I don’t know if you can understand the meaning of thread switching and ANR. If you don’t get it, cut me down

Stars and ANR

Many interviewers like to ask, Looper’s loop method is an infinite loop, and the loop method runs on the main thread, why does the main thread have an infinite loop does not cause ANR?

In fact, an interesting point in this is that many people regard Looper’s loop method as a common method, so there is such a question. But this loop method is not a run-of-the-mill method.

So let’s think about it a little bit, if we write an app and we don’t write a line of code in it, will the app crash?

The obvious answer is no.

However, as mentioned above, an App is essentially a Java program, and Java programs have a main method, and the ActivityThread class does have a main method. When we write a Java program, the program ends when the code in the main method is finished. But the app doesn’t, it keeps running as long as you don’t quit. So why is that?

I think a lot of you might think, yeah, if you don’t exit, if you write an infinite loop, then the code in main never runs out, and then the program doesn’t exit by itself. Android certainly does the same thing, and not just Android, but almost all GUI programs do the same thing. It is the looper. loop method that blocks the main thread, so our app does not exit. So you might be wondering, well, if I have an infinite loop here, how does the rest of my code work? What about interface interaction? You’ve asked the right question.

Essentially, Android is an event-driven program. Interface refresh and interaction are essentially events, and these events are finally sent to MessageQueue as messages. Looper distributes it and then processes it. In human terms, our Android applications run in this endless loop. Once the loop ends, the app ends.

So what is ANR? ANR is the Application Not Responding or the Android program does Not respond. Why is there no response? Because the main thread is doing a time-consuming operation. Looper’s loop method blocks the main thread, so why not ANR? So let’s talk about, what is a response? The response is the refresh of the interface, the processing of the interaction and so on. So who responded to this response? Yes, that’s the loop method that responds. What do you mean, no response? The loop method is blocked, preventing other messages from being processed.

So the conclusion is that the main thread does not block the main thread by doing time-consuming operations, but rather blocks Looper’s loop method. As a result, the loop method cannot handle other events, resulting in ANR events. By contrast, it is because Of Xiaoming’s endless homework correction in the office, which occupies the teacher’s time and makes the teacher unable to correct the next student’s homework, that the teaching activities cannot be carried out normally. And the teacher constantly correcting homework, which itself is a normal teaching activity, but also because the teacher constantly correcting homework, students have improved, teaching can continue.

Handler has a few caveats at the Java layer

  1. The child thread which

    To create a Handler, we must create a Looper using the looper.prepare () method. The ActivityThread has already created the Looper for us in the main thread. We do not need to create the Looper ourselves, but if we create the Handler in the child thread, Either use mainLooper of Looper, or create Looper objects that belong to this thread by calling looper.prepare () yourself. Here is a Looper object that creates a child thread:

    class LooperThread extends Thread {
        public Handler mHandler;
        public void run(a) {
            Looper.prepare();  
            mHandler = new Handler() {  
                public void handleMessage(Message msg) {
                    //TODO defines the message processing logic.}}; Looper.loop(); }}Copy the code
  2. The message pool

    When generating messages, it is best to obtain a Message using message.obtain (). Why?

    // Message.java
    
    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

    As you can see, the obtain method empties all the data from a Message object and then adds it to the header of the list. SPool is a message pool with a default cache of 50. And the last line in Looper’s loop method looks like this

    msg.recycleUnchecked();
    Copy the code

    Looper recycles the used messages after distribution and adds them to the collection pool.

  3. Memory leakage caused by handlers. Procedure

    What is a memory leak? In short, what should be recycled is not recycled. This is typically used in the Handler:

    class HandlerActivity: AppCompatActivity(a){
    
        private val mHandler = MyHandler()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // Send messages through custom handlers in child threads
            thread {
                 mHandler.sendEmptyMessageDelayed(1.1000)}}// Create a custom Handler
        class MyHandler: Handler(a){
            override fun handleMessage(msg: Message) {
                Log.i("HandlerActivity".HandleMessage: ${msg.what})}}}Copy the code

    At first glance, there is no problem, but have you ever wondered if the handleMessage method will still be implemented before the app is launched? The answer is yes. Why is that? Why would I do that when I quit? It has something to do with Java

    MyHandler is an inner class of HandlerActivity that holds a reference to the HandlerActivity.

    After entering the page, a Message is sent that is delayed for 1s. If the HandlerActivity exits within 1s, since the Handler will be held by Message in its target variable, and Message will be stored in the Message queue, This sequence of associations causes the HandlerActivity to be held when it exits and therefore cannot be collected by the GC. This is a memory leak! When the 1s delay message is finished executing, the HandlerActivity is recycled.

    Although the result will still be recycled, the memory leak problem must be solved. How to solve it?

    1. Change MyHandler to a static class so that it no longer holds references to external classes. HandlerActivity can be used as a weak reference in MyHandler, which can be recycled when the page exits.

    2. When the page exits onDestroy, call Handler’s removeMessages method to remove all messages, which also removes the holding chain.

  4. Synchronous message barrier

    What is a synchronous message barrier?

    Call Message MSG = queue.next() in Looper’s loop method; The next method of MessageQueue has this line:

    //MessageQueue.java
    // Omit some code
    Message next(a) {
          
            for (;;) {
                if(nextPollTimeoutMillis ! =0) {
                    Binder.flushPendingCommands();
                }
                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;
                    // The line 1 is critical, where the key point of the synchronization message barrier is
                    if(msg ! =null && msg.target == null) {
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while(msg ! =null&&! msg.isAsynchronous()); }}}}Copy the code

    The line below comment 1 first checks that MSG is not NULL, and then checks that MSG target is null. We know that a message target is the handler that sends it. All messages have a handler. For synchronous messages, it’s true that all messages have handlers, whereas in this case asynchronous messages. A message that satisfies target == NULL is an asynchronous message. Synchronization barriers are used to block synchronous message execution. Good point. So what does a synchronization barrier do?

    It seems that synchronization barriers are rarely used in everyday application development. So, what are the scenarios where synchronization barriers are used in the system source code? UI update messages in the Android system are asynchronous messages and need to be processed first. In short, if the user (developer) inserts a very time-consuming message into the queue before starting drawing, the UI will not draw on time, resulting in frame lag. Synchronous message barriers can be used to ensure priority for UI drawing.

The Handler in the c + +

If your goal is to understand how handlers are implemented in the Java layer, you don’t need to look below. Here’s how the Handler works and is implemented at the C++ layer.

First of all, careful friends may have questions. Looper is always in an endless loop, just like the teacher keeps asking the study committee to correct homework. The teacher is also a human being, won’t he be tired? You are right. Of course, the teacher will not constantly ask the study committee for homework. Under normal circumstances, after someone has handed in homework, the study committee will send it to the teacher to correct. When there is no homework, the teacher may be resting or playing games. The same is true for Looper, which actually sleeps when the message queue is empty and is only woken up when a Handler sends a message **. So how does that work?

In the whole message mechanism, MessageQueue is the link between Java layer and Native layer. In other words, Java layer can add messages to MessageQueue MessageQueue, and Native layer can add messages to MessageQueue MessageQueue.

This is the Native method in MessageQueue:

// MessageQueue.java

private native static long nativeInit(a);
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); 
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
Copy the code

In the MessageQueue constructor it looks like this:

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
}
Copy the code

NativeInit method is called to create MessageQueue of native layer. MPtr is the pointer that stores NativeMessageQueue. Subsequent thread suspension and thread wake up are accomplished through this pointer. In fact, this is done through the MessageQueue of the Native layer.

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // Initialize the native message queue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(a); nativeMessageQueue->incStrong(env); // Increase the reference count
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
Copy the code

Here is the constructor for NativeMessageQueue:

//android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue()
            : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {

    mLooper = Looper::getForThread(a);// Function analogies to Java layer looper.mylooper ();
    if (mLooper == NULL) {
        mLooper = new Looper(false); // Create Looper for native layer
        Looper::setForThread(mLooper); // Save native layer Looper to TLS, function similar to Java layer threadlocal.set ();}}Copy the code

Looper is constructed like this:

//Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(- 1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK); // Construct the fd of the wake up event
    AutoMutex _l(mLock);
    rebuildEpollLocked(a);// Rebuild the Epoll event
}

void Looper::rebuildEpollLocked(a) {
    if (mEpollFd >= 0) {
        close(mEpollFd); // Close the old epoll instance
    }
    mEpollFd = epoll_create(EPOLL_SIZE_HINT); // Create a new epoll instance and register the WAKE pipe
    struct epoll_event eventItem;// Create a wake up listener
    memset(& eventItem, 0.sizeof(epoll_event)); // Set the unused data area to 0
    eventItem.events = EPOLLIN; // Set the listener readable event
    eventItem.data.fd = mWakeEventFd;
    // Add the wake event (mWakeEventFd) to the epoll instance (mEpollFd)
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

    for (size_t i = 0; i < mRequests.size(a); i++) {const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        // Add events from the Request queue to the epoll instance, respectively
        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem); }}Copy the code

Wait, you come up here and give me this big piece of C++ code. I can’t possibly understand it. And what is this epoll? Isn’t it about how Looper sleeps and wakes up?

Yes, that’s how Looper sleeps and wakes up. Looper sleep and wake up are implemented in the Native layer based on the Epoll mechanism on Linux.

What is the epoll mechanism?

Epoll can be simply understood as a listening event. On Linux, I listen for an event through the epoll mechanism. When nothing happens, I let the CPU go to sleep, and when the event is triggered, I wake up from sleep and start processing it. Just like the button click event, once clicked, listening for the click event triggers the button’s onClick method. But on LInxu it’s done by reading and writing files. Analogy to

include <sys/epoll.h>

// Creating a handle initializes onClickListener
int epoll_create(int size);
// Add/remove/modify the listener event equivalent to addOnClicklistener
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
This is equivalent to the onCLick method
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Copy the code

Epoll_wait is similar to the onCLick method in Java in that it receives the result when the file it is listening for changes. It’s more like the suspend method in the Kotlin coroutine, where you wait, you block, and then you move on. The onClick method is implemented in the form of an interface callback and is non-blocking. The epoll_wait method blocks.

In the Looper constructor above, the rebuildEpollLocked method is called, which sets the listener, known as setOnClickListener, but listens for the readable events of the file. Namely eventItem. Events = EPOLLIN; This line of code. What are readable events? That is to say, once there is something in the file, you can read it

Okay, the incident has been monitored, so where did Looper sleep?

This is the line in MessageQueue:

//MessageQueue.java
Message next(a) {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    for(;;) {... nativePollOnce(ptr, nextPollTimeoutMillis);// Block the operation. }Copy the code

This is the line of code that blocks.

The invocation relationship looks like this:

MessageQueue::nativePollOnce ->NativeMessageQueue::pollOnce()->Looper::pollOnce()->Looper::pollInner

int Looper::pollInner(int timeoutMillis) {

   

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    If the nativeWake() method writes characters to the pipe writer, the method returns;
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    mPolling = false;

    mLock.lock(a);if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked(a);goto Done;
    }

    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = POLL_ERROR;
        goto Done;
    }

    if (eventCount == 0) {
        result = POLL_TIMEOUT;
        goto Done;
    }

    // Loop through to handle all events
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        // Wake up event
        if (fd == mWakeEventFd.get()) {
            if (epollEvents & EPOLLIN) {
                //// has been awakened, reads and empties pipe data [7]
                awoken(a); }else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); }}else {
            // Handle other events, Handler does not
            // omit some code...
        }
    }
Done: ;

    // omit some code...
    
    // Release lock.
    mLock.unlock(a);// omit some code...
    return result;
}
Copy the code

The code starts blocking at comment 1, which is called sleep. So when can we wake it up? Time out, or the file has changed, you can read and wake up. Note that the timeout is a delay set at the Java layer, which means that Java’s sendMessageDelayed method ends up with a delay set by ePoll.

I don’t know if you’ve noticed, but when we send Message, we have this line of code:

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // omit some code...
    
    synchronized (this) {
     
        msg.markInUse();
        msg.when = when;
        
        // Get the head of the queue
        Message p = mMessages;
        boolean needWake;
        
        // If the message does not need to be delayed, or the execution time of the message is earlier than the header message, it is inserted into the queue head
        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 {
            // The message is inserted into the middle of the queue
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            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;
        }

        if (needWake) {
            // Tap the blackboard to underline: wake upnativeWake(mPtr); }}return true;
}
Copy the code

In the final nativeWake (mPtr); This line of code wakes you up. If neekWake is true, it will wake up.

There are two ways to wake up a thread:

  1. (the queue is empty | | message without delay | | or message execution time earlier than the message queue head) && (thread is in a pending state (mBlocked = true))
  2. Thread suspension (mBlocked = trueThe message loop is in the synchronous barrier state and needs to wake up if an asynchronous message is inserted.

How exactly does the wake up operation work?

The call chain looks like this:

MessageQueue::nativeWake—>android_os_MessageQueue_nativeWake()—>NativeMessageQueue::wake()—>Looper::wake()

//Looper.cpp
void Looper::wake(a) {
    uint64_t inc = 1;
    // Write the character 1 to the pipe mWakeEventFd
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if(nWrite ! =sizeof(uint64_t)) {
        if(errno ! = EAGAIN) {ALOGW("Could not write wake signal, errno=%d", errno); }}}Copy the code

Epoll_wait is listening for a readable event in the file, so now that a message comes in, I can trigger the event by writing something into the file. Looper is just a 1 character. Successfully woke up the thread. Polling is then initiated to retrieve message distribution.

conclusion

Handler also has its own message polling mechanism at the C++ layer, which is basically the same as Java, so we won’t analyze it here.

Handler is the foundation of the entire Android system, and it’s Looper’s endless loop that keeps Android applications from exiting. All UI interactions, such as screen refreshes, are events that are sent to Looper by Handler for distribution. The entire Android app runs in this endless loop.

Looper is the teacher who keeps correcting homework, MessageQueue is the study committee member who reminds me to hand in homework, Message is the homework whose name is written on it, and Handler is xiaoming.

There can be only one Looper and only one MessageQueue in a thread, but there can be multiple handlers. MessageQueue can also handle messages from multiple handlers.

Looper is woken up and suspended by the Epoll mechanism in Linux. It is woken up by listening for readable events of files.

In the whole process, MessageQueue is the link to realize the interaction between Java layer and C++ layer, and Native methods are basically realized by MessageQueue.

The binding of a Handler to a thread depends on a map in ThreadLocal. In addition, the Message processing process is to process Native Message first, then Native Request, and finally Java Message. Once you understand the process, you can see the real reason why sometimes there are few upper-layer messages but the response time is long.

Refer to and acknowledge:

Developer’s Cat

gityuan