1. Ask questions

Common interview questions:

  • This section describes the Android messaging mechanism
  • Handler, Looper, MessageQueue, Message

Handler, Looper, MessageQueue, Message, Looper, Looper, Message So to be more specific:

  • Why can I use Handler directly on the main thread?
  • How is the Looper object bound to MessageQueue?
  • Where do messages in MessageQueue come from? How does a Handler insert a message into a MessageQueue?
  • How is Message bound to Handler?
  • How does the Handler bind MessageQueue?
  • So handler, what thread is new handler under anywhere?
  • What does the Looper loop do when it gets the message?

Second, solve the problem

So let’s start with the message mechanism of the main thread:

2.1 Creation and Looper of the main thread

The entry point to the Android application is the main function, where the main thread Looper is created.

ActivityThread –> main() function

public static void main(a){
        // step1: create the main thread Looper object
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        // Bind application process, Boolean flag whether it is system process
        thread.attach(false);
        // instantiate the main thread Handler
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: start the loop
        Loop.loop();

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

Looper.preparemainlooper () is the Looper object used to create the main thread. Let’s see how this method is implemented.

2.1.1 Creating the main thread Looper

Looper –> prepareMainLooper()

private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper(a){
        // step1: Call the prepare method of this class
        prepare(false);
        // Thread synchronization, if the variable sMainLooper is not null throw the main thread Looper has been created
        synchronized (Looper.class) {
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // step2: call the myLooper method of this classsMainLooper = myLooper(); }}Copy the code

The prepareMainLooper() method creates a Looper object for the current thread using prepare(false), and then obtains the Looper object for the current thread using myLooper().

step1: Looper –> prepare()

// ThreadLocal holds separate variables for each thread
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// MessageQueue variable of the Looper class
final MessageQueue mQueue;
// quitAllowed Whether to exit, where the main thread Looper cannot exit
private static void prepare(boolean quitAllowed) {
        // First check if Looper exists
        if(sThreadLocal.get() ! =null) {throw new RuntimeException("Only one Looper may be created per thread");
        }
        // Save the thread copy variable
        sThreadLoacal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code
  • The prepare() method uses ThreadLocal to hold the main thread’s Looper object. ThreadLocal can be thought of as a class for storing data, similar to collection classes such as HashMap and ArrayList, which hold variables belonging to the current thread.

  • ThreadLocal provides get/set methods for retrieving and saving variables, respectively. For example, on the main thread, create a Looper object by using prepare() and use sthreadLoacal.set (new Looper(quitAllowed)) to save the main thread Looper object. So calling myLooper() on the main thread (which actually calls sthreadLocal.get ()) gets the main thread’s Looper object from ThreadLocal. If you call these methods on a child thread, you will use ThreadLocal to hold and retrieve Looper objects belonging to the child thread.

More on how ThreadLocal works:

An in-depth look at ThreadLocal implementation and memory leaks

Question 1: Why can I use Handler directly on the main thread? Since the main thread has already created the Looper object and started the message loop, this can be seen from the code above.

Question 2: How is the Looper object bound to MessageQueue? Or the Looper object creates the MessageQueue procedure. Simply, Looper has a member variable, mQueue, which is the MessageQueue that the Looper object holds by default. Looper has a constructor in the above code. When a new Looper object is created, MessageQueue is directly created and assigned to mQueue. Problem 2 resolved: The MessageQueue object was created at New Looper and assigned to the Looper member variable mQueue.

step2: Looper –> myLooper()

// The ThreadLocal object of this class is used to retrieve the Looper object created previously
public static @Nullable Looper myLooper(a) {
     return sThreadLocal.get();
}
Copy the code

This method is the sThreadLocal variable to get the current thread Looper object, a common method. This method is used to retrieve the main thread Looper object created above.

2.1.2 Start processing messages in a loop

Returning to the original main() function, looper.loop () is called after the Looper object is created to loop through the message.

public static void main(a){
        // step1: create the main thread Looper objectLooper.prepareMainLooper(); .// step2: start the loop
        Loop.loop();
}
Copy the code

Looper –> loop()

public static void loop(a) {
    // step1: Get the Looper object of the current thread
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // step2: get the MessageQueue object saved by Looper
    finalMessageQueue queue = me.mQueue; .// step3: read the message loop, if there is, call the handler stored in the message object to send
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }...try {
            // Step4: Use the handler object held by the Message object to process the Message
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0)?0 : SystemClock.uptimeMillis();
        } finally {
            if(traceTag ! =0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked(); }}Copy the code
  • Step1: the myLooper() method is used to retrieve the Looper object of the current thread from ThreadLocal. Note that the Looper object of the current thread is retrieved from the thread that uses this method.
  • Step2: me. MQueue. This mQueue is the MessageQueue variable created when the Looper object is created as mentioned in Question 2 above.
  • Step3: next is a for loop, which first fetches the next message through queue.next(). See section 4.2 of the following article for details:

Android Messaging 1-Handler(Java Layer)

The next message is retrieved, and if there is no message in the MessageQueue, it blocks. So if there is a message, how does it get into MessageQueue? Or where do the messages in MessageQueue come from? How does a Handler insert a message into a MessageQueue? So let’s not talk about that, but let’s call this problem problem number three.

  • Step4: MSG. Target. DispatchMessage (MSG); This method eventually calls the handleMessage(MSG) method of the Handler. This raises the question: when is MSG. Target assigned? ****, that is, how does Message bind to Handler? Let’s call it problem number four. Now look at Handler’s dispatchMessage method:

Handler –> dispatchMessage

public void dispatchMessage(Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}private static void handleCallback(Message message) {
   message.callback.run();
}

public void handleMessage(Message msg) {}Copy the code

You can see that the method finally executes the handleMessage() method, which is an empty method that we need to override and implement. DispatchMessage () also shows a problem:

Priority of message distribution:

  • Message callback method: message.callback.run(); Highest priority;
  • Handler: McAllback.handlemessage (MSG) has the upper priority;
  • Handler’s callback method: handleMessage() has the lowest priority.

The Looper loop is used to send messages to the Handler, and the Handler is used to send messages to the Handler.

2.2 Creation and Functions of Handler

The loop() method continually fetches messages from MessageQueue (queue.next()), blocks if there are no messages, and passes them on to the message-bound Handler. Let’s review two unresolved issues:

– Question 3: Where do messages in MessageQueue come from? How does a Handler insert a message into a MessageQueue? – Question 4: When is msg.target assigned? That is, how does Message bind to Handler?

To solve the problem of the Handler inserting the message, we need to look at the Handler sending the message.

2.2.1 Handler Sends messages

Handler –> sendMessage(Message msg);

final MessageQueue mQueue;
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
// Send a delayed message
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// Specify a time to send the message
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);
}
// Process the Message, assign the target of the Message object, and the Message queue inserts the Message
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

You can see that calling the sendMessage(Message MSG) method ends up calling the enqueueMessage() method, which has two main functions: assigning the target of the Message object and inserting the Message into the Message queue.

  • MSG: MSG. Target = this Assigns the message sending Handler to the target of the MSG object. Problem 4 is solved: The Handler binds itself to the Message target while sending the Message, thus creating a connection between the two.
  • Message queue inserts message: Queue. EnqueueMessage (MSG, uptimeMillis) queue is an instance of MessageQueue, Queue.enqueuemessage (MSG, uptimeMillis) is the enqueueMessage method that executes MessageQueue to insert messages. The answer to question 3 is this: The Handler executes the enqueueMessage method of MessageQueue to insert the message when sending it. For information on how MessageQueue performs the insertion process, see section 4.3 below

Android Messaging 1-Handler(Java Layer)

  • MessageQueue queue (sendMessageAtTime, mQueue); MessageQueue queue (sendMessageAtTime); So where does mQueue come from? Question 5: How does the Handler bind MessageQueue? Handler is bound to Looper’s MessageQueue object, which is new when Looper is created. To see how the Handler’s MessageQueue object is assigned, look at the Handler constructor. When the Handler is created, it performs a series of operations such as retrieving the current thread’s Looper, binding the MessageQueue object, and so on.

2.2.2 Creating a Handler

Below are the Handler no-argument constructor and the main constructor, along with several other overloaded constructors some that call the two-argument constructor by passing different arguments. The first argument is the callback callback, and the second function marks whether the message is asynchronous.

// No parameter constructor
public Handler(a) {
     this(null.false);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &amp; &amp; (klass.getModifiers() &amp; Modifier.STATIC) ==0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: "+ klass.getCanonicalName()); }}// step1: Get the current thread Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // Step2: Get the MessageQueue bound to Looper and assign it to Handler's mQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code
  • Step1: call the myLooper() method, which uses the sThreadLocal object to get the Looper object of the current thread.
public static @Nullable Looper myLooper(a) {
     return sThreadLocal.get();
}
Copy the code

If the Looper object is null, looper.prepare () was not executed to save the Looper variable for the current thread, and a RuntimeException is thrown. Handler must be used on a thread that has a Looper. Without a Looper, you cannot bind the MessageQueue object and therefore cannot perform any more operations on the message.

  • Step2: mQueue = mLooper. MQueue indicates that the MessageQueue object of the Handler is assigned by the MessageQueue object of the current thread Looper. Here problem 5 is resolved: The Handler is created to bind the MessageQueue object of the current thread Looper.

  • Since Handler and Looper can be thought of as using the same MessageQueue object, Handler and Looper can share the MessageQueue MessageQueue. Handler sends messages (using the mQueue to insert messages into the pair column). Looper can easily loop through the mQueue to retrieve messages. If a Message is found, Looper can process the Message using the target Handler object bound to the Message object.

Now that we’re talking about Handler constructors, we have a question: Question 6: What thread is new Handler under anywhere? This question depends on whether to pass a Looper object.

  • Create Handler without passing Looper: Handler Handler = new Handler(); MyLooper () is used to retrieve the Looper object. MyLooper () is used to retrieve the Looper object. The thread on which the Handler is created gets the Looper object by default, and that is where the Handler’s operations are performed.

  • Handler Handler = new Handler(Looper); So look at the constructor passed in for Looper:

public Handler(Looper looper) {
    this(looper, null.false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
// The first argument is the looper object, the second callback object, and the third message handling method (whether asynchronous or not)
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

As you can see, passing the Looper object Handler is used directly. So in the case of passing a Looper object to create a Handler, the thread to which the Looper is passed is the thread to which the Handler is bound.

Now that Looper and Handler have an overview of the process, let’s look at a simple child thread Handler example:

new Thread() {
    @Override
    public void run(a) {
        // step1
        Looper.prepare();
         // step2
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == 1){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run(a) {
                            Toast.makeText(MainActivity.this."HandlerTest",Toast.LENGTH_SHORT).show(); }});// step5Looper.myLooper().quit(); }}};// step3
        handler.sendEmptyMessage(1);
         // step4
        Looper.loop();
    }
}.start();
Copy the code
  • Step1: call stars. Prepare (); Creating a Looper object for the current thread creates MessageQueue, which then stores the Looper object for that thread in ThreadLocal. Note that everything is done in the child thread and using Handler without calling looper.prepare () will cause an error.
  • Step2: Create a Handler object and override handleMessage to process messages. Call this method when waiting for messages sent by this Handler to process.
  • Step3: Use handler to send messages. This is just an example. After all, there is no need to send messages to yourself. It assigns itself to msg.target and inserts the message into the MessageQueue object bound to Looper.
  • Step4: call stars. The loop (); First get the current thread Looper object, according to the Looper object can get the MessageQueue object mQueue saved by Looper. The for loop retrieves the Message object it holds and returns a NULL block if the Message does not exist, otherwise it uses the Handler stored in Message: MSG. Target is used to process the message, and eventually handleMessage, the overridden method, is called to process the message.
  • Step5: quit should be used at the end of the logic process to terminate the message loop, otherwise the child thread will remain in a waiting state, and if Looper is quit, the thread will terminate immediately. Therefore, it is recommended to terminate Looper when it is not needed.

Summary and others

3.1 Handler, Looper, MessageQueue, Message

  • Handler is used to send messages. It first obtains the default or passed Looper object and holds the MessageQueue contained in the Looper object. When sending messages, the MessageQueue object is used to insert messages and encapsulate itself into specific messages.
  • Looper is used to loop messages for a thread. Looper holds a MessageQueue object, mQueue, which can be looped to retrieve messages maintained by -Messagequeue. If the retrieved MessageQueue has no Message, it blocks in the nativePollOnce() method of the loop’s queue.next(), otherwise it wakes up the main thread to continue working, which is then processed using the message-wrapped handler object.
  • MessageQueue is a MessageQueue that does not add messages directly, but via a Handler object associated with Looper.
  • Message contains data and information to be passed.

3.2 Why doesn’t the main thread in Android freeze due to an infinite loop in looper.loop ()?

This is a question from Zhihu, and it feels interesting to ask. You probably don’t dig too deeply into these questions, but if someone answers, take notes.

  • Why doesn’t it get stuck in an endless loop? A thread can be thought of as a piece of executable code whose life cycle ends when the code completes execution. For the main thread, we don’t want it to exit after a certain amount of time, so the simple thing is that the executable code can always execute, and an infinite loop is guaranteed not to exit. So how do you handle the message if it’s an infinite loop, by creating a new thread.
  • A new thread is prepared for the loop. New binder threads are created prior to the loop, in code activityThread.main () :
public static void main(a){... Looper.prepareMainLooper();// Create an ActivityThread object
        ActivityThread thread = new ActivityThread();

        // Create a new thread.
        thread.attach(false);

        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: start the loop
        Loop.loop();

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

Thread. The attach (false); A Binder thread (ApplicationThread, the server side of Binder that receives events from AMS) is created, which sends messages to the main thread through a Handler.

  • Is the main thread running in an endless loop particularly CPU intensive? Pipe /epoll blocks queue. NextAndroid 1-handler (Java layer) when the main thread MessageQueue does not have a message. The corresponding program is immediately informed to read or write operations, essentially synchronous I/O, that is, read and write is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU.

  • How is the Activity lifecycle implemented to be able to execute outside of the dead loop? The main function gets the sMainThreadHandler as follows:

final H mH = new H();

public static void main(a){...if(sMainThreadHandler == null){ sMainThreadHandler = thread.getHandler(); }... }final Handler getHandler(a) {
    return mH;
}
Copy the code

Class H inherits the Handler that is created when the main thread is created to handle messages sent by the Binder threads.

The Activity life cycle depends on the main thread looper. loop, which takes action when different messages are received:

In the h.handleMessage (MSG) method, the corresponding lifecycle is executed depending on the MSG received. Such as received MSG = H.L AUNCH_ACTIVITY, call the ActivityThread. HandleLaunchActivity () method, which will eventually through reflection mechanism, create the Activity instance, and then execute the Activity. The onCreate () method; Such as MSG = have received H.P AUSE_ACTIVITY, call the ActivityThread. HandlePauseActivity () method, which will eventually perform the Activity. The onPause () method. I’m just going to pick the core logic, but it’s much more complicated than that.

3.3 Memory Leaks Caused by Handler Usage

  • There are delayed Messages that need to be removed when the Activity is destroyed
  • Leaks caused by anonymous inner classes are changed to anonymous static inner classes, and weak references are made to the context or Activity.

The last

You can click “like” to pay attention to the next, will update more selected technology to share with you!