Principle analysis of [Androidj advanced] Handler mechanism

Handler is an asynchronous callback mechanism provided in Android, which can also be understood as a message mechanism between threads. In order to avoid ANR, we usually put some time-consuming operations (such as network request, I/O operation, complex calculation, etc.) into the sub thread for execution. When the sub thread needs to modify the UI, the sub thread needs to notify the main thread to complete the operation of modifying the UI. At this time, we need to use the handler mechanism to complete the communication between the sub thread and the main thread.

General steps for using Handler
After clarifying the premise that only the main thread can modify the UI interface and the sub thread can perform time-consuming operations in Android, let's learn the steps of using the Handler.
Create a Handler instance in the main thread and override the handlerMessage method.

 private Handler handler = new Handler(){
 @Override
 public void handleMessage(Message msg) {
     switch (msg.what) {
         case 1:
            //Perform relevant UI modification operations
             break;
        }
    }
 };

2. Get the handler object from the sub thread and use the handler to send messages where the UI update operation needs to be performed

 Message msg = Message.obtain();
 msg.obj = "content";
 msg.what = 1;
 //Send message to Handler
 handler.sendMessage(msg);

3. In the Switch in the handlerMessage method, perform relevant operations according to different constants under case

The above is only one way of using Handler. Since the focus of this article is to explore the principle of Handler, other ways of using Handler are not introduced here.

Responsibilities of important categories
Before understanding the principle of Handler mechanism, we should clarify the responsibilities of several important classes in Handler mechanism.
Handler: responsible for sending and processing messages
MessageQueue: message queue, which is responsible for storing messages
Message: specific message sent
Looper: responsible for loop fetching messages to Handler for processing
ThreadLocal: used for data isolation between threads. Each thread stores its corresponding Looper

Principle of Handler mechanism

Each Handler will be associated with a message queue, which is encapsulated in the Looper object, and each Looper will be associated with a thread. In this way, Handler, message queue and thread are associated.

Handler is a message processor that sends messages to the message queue, and then the corresponding thread takes them out one by one from the message queue and executes them.

By default, there is only one message queue, that is, the message queue of the main thread. The message queue is created through the Looper.prepareMainLooper() method. Finally, loop. Loop() is executed to start the message cycle.

The above description may be a little empty. Let's understand it in combination with the source code:

3.1 how does the handler associate message queues and threads?
Let's first look at the default constructor of Handler:

     public Handler(Callback callback, boolean async) {
    //Code omission
    mLooper = Looper.myLooper();//Get Looper
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
        mQueue = mLooper.mQueue;//Get message queue through Looper object
        mCallback = callback;
        mAsynchronous = async;
    }
    
    //Get Looper object
    public final Looper getLooper() {
        return mLooper;
     }

From the constructor of the Handler, we can find that while initializing, the Handler will obtain a Looper object through Looper.getLooper(), associate it with the Looper, and then obtain the message queue through the Looper object. Let's go deep into the Looper source code to see how Looper.getLooper() is implemented.

    //Initializes the current thread Looper
    public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
        }
     }

    //Set a Looper for the current thread
    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));
     }

From the above program, we can see that through prepareMainLooper(), and then call prepare(boolean quitAllowed) method to create a Looper object, and through sThreadLocal.set(new Looper(quitAllowed)) method to set the object to sThreadLocal.

    //Get Looper through ThreadLocal
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
     }

Through the preparatory work in Looper, a Looper object has been stored in sThreadLocal, and then the myloop () method obtains the Looper through the sThreadLocal.get() method. Then the message queue is associated with the thread, so each thread can only access its own message queue.

To sum up, we can find that the message queue is associated with the thread through the Looper, and the Looper is associated with the Handler, so the Handler is associated with the message queue of the thread and thread.

3.2 how to execute a message loop?
After creating the Looper object, how is the message sent through the Handler processed after it is placed in the message queue? This involves the message loop, which is established through the loop. Loop () method. The source code is as follows:

    //Execute message loop
    public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//Get message queue
    //Code omission
    for (;;) {//Dead cycle
        Message msg = queue.next(); // Get message
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //Code omission
        try {
            msg.target.dispatchMessage(msg);//Distribute messages
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        //Code omission
        msg.recycleUnchecked();//Message recycling
         }
    } 

From the source code, we can see that the loop() method is essentially a process of continuously obtaining messages from the message queue through an endless loop, and then continuously processing messages.

3.3 message processing mechanism

msg.target.dispatchMessage(msg);// Distribute messages

Let's start with the dispatchMessage() method in the loop to see who is the caller of this method, and go deep into the Message source code to see the specific type of target:

    public final class Message implements Parcelable {
        //Code omission

        /*package*/ int flags;
        /*package*/ long when;
        /*package*/ Bundle data;
        /*package*/ Handler target;
        /*package*/ Runnable callback;
        /*package*/ Message next;

        //Code omission
    }

From the source code, we can see that the target is actually the Handler type. Therefore, the Handler sends the message to the message queue for temporary storage, and then sends the message to the Handler itself for processing. Let's continue to the Handler source code to see how the Handler handles messages:

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();
}
/**
 * Subclasses must implement this to receive messages.
 * The message processing method is an empty method, which is implemented by subclasses
 */
public void handleMessage(Message msg) {
}

As can be seen from the above source code, dispatchMessage(Message msg) is only responsible for distributing messages. From the Message source code, we can know that the callback is of Runnable type. If the callback is not empty, execute the handleCallback method for processing, and this method will call callback.run(); If callback is empty, handleMessage will be called to process the Message, and the method is empty, so we will override the method in the subclass and write the code to modify the UI in it. These two situations occur because the Handler sends messages in two forms:

In the general steps of this article, I use sendMessage(msg) to send messages, and the callback is empty.
When sending a message using post, the callback is not empty.
The above is the principle of the Handler mechanism, which can be roughly summarized as follows: in the sub thread, the Handler sends messages to the MessageQueue, then the Looper constantly reads messages from the MessageQueue, calls the Handler's dispatchMessage to send messages, and finally the Handler processes the messages. In order to help you understand better, I drew a schematic diagram of the Handler mechanism:

The following points are added to the Handler mechanism:

The Handler uses the message pool when creating messages. When creating messages, it will first query whether there are message objects from the message pool. If there are, it will directly use the objects in the message pool. If not, it will create a new message object.
The purpose of using ThreadLocal is to ensure that each thread creates only one Looper. Then, when other handlers are initialized, directly obtain the Looper created by the first Handler.

Finally: the follow-up will be continuously updated. If you like it, please pay attention to it.

Related video
[Android source code analysis] Handler source code analysis and key questions in large factory interview

Keywords: Android Programmer IT Handler

Added by hucklebezzer on Tue, 07 Dec 2021 20:27:07 +0200