Detailed explanation of Android Handler mechanism

Posted by matbennett on Sat, 01 Jan 2022 16:51:40 +0100

android Handler mechanism principle analysis (one article is enough, including your image and depth)

Principle and mechanism analysis of android The column contains this content
2 articles 1 subscription

First, I describe the principles and mechanisms related to Handler as the following scenarios:

  • Handler: Courier (employee of a courier company)
  • Message: package (a box that can hold many things)
  • MessageQueue: Express sorting center (conveyor belt for sorting express)
  • Looper: Express Company (with a management center to handle the destination of packages)

Scenario analysis: update the UI of the main thread in the child thread

The principle and mechanism can be vividly understood as:

One day, you want to send a gift to your friend. First, you take a box to pack the gift and wrap it. When you place an order, you call a courier to pick it up. After the courier receives your package, he will send the package to the express sorting center and wait for the delivery car to send your package. When the delivery car comes, send it to the designated local site according to your package address information, and then assign it to the corresponding courier to deliver your package to your friends.

The whole process of mailing packages can be vividly understood as the working mechanism and principle of the Handler. The actual working process is restored below:

When you want to refresh the TextView of the main interface, but you are not in the main thread, you will wrap the Message and declare a Handler, Let the Handler send your Message to the Looper. After the Handler sends your Message to the Looper, it still needs to wait in line. When it's your turn, the main thread will tell the Handler that the Message can be processed and you are responsible for distributing it. Then, the Handler distributes the Message to the corresponding callback or handleMessage() method. Then, You update the UI in this method.

Next, I will introduce the four big men one by one.

1, Message

Message.class is located on Android os. In the bag. The constructor of message is a parameterless constructor, and there is only one constructor;

public Message() { }

     
  • 1

In addition to the construction method, you can also create instance objects through internal static methods:

static Message obtain()
static Message obtain(Message orig)
static Message obtain(Handler h)
static Message obtain(Handler h, Runnable callback)
static Message obtain(Handler h, int what)
static Message obtain(Handler h, int what, Object obj)
static Message obtain(Handler h, int what, int arg1, int arg2)
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

In the above static methods, the first method will be called to create a Message object. Let's take a look at the source code (API 28)

	public static final Object sPoolSync = new Object();    //Synchronization lock object
    private static Message sPool;                           //Global pool message instance
/**
 * Returning a new message instance from the global pool allows us to avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            //......
            return m;
        }
    }
    return new Message();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

If the Message instance of the current global pool is not empty, the first Message instance will be returned. Therefore, in most cases, using obtain() to obtain a Message object can avoid consuming more memory resources.

Through the source code, we can find that other overloaded methods of static obtain() are all assignment operations without much discussion. The only thing to note is the static method of obtain(Handler h, Runnable callback):

	/*package*/ Handler target;
	/*package*/ Runnable callback;
public static Message obtain(Handler h, Runnable callback) {
    Message m = obtain();
    m.target = h;
    m.callback = callback;
    return m;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

As you can see, it is also an assignment operation. Target is a member variable of the protection level, that is, only the same package name space can be accessed. This variable is of great significance. In addition to the target, there is also a callback, which works in conjunction with the Handler. The Handler will explain later. Please take it easy.

At this point, we should first remember the following points:

  • Message has 8 static methods to create message instances
  • Message has two important member variables: target and callback. One is Handler and the other is Runnable.
  • Message has four public variables what, arg1, arg2 and obj, which can store messages for delivery
  • Message also has a compartment member variable next, which is of message type and will be used later. Just know that there is this next

The above is the basic secret of Message. It is very simple and there is nothing complicated (as a parcel box, it is so simple that it can hold some things and then attach some key information).

2, Handler

Handler.class is also located on Android OS package. Handler means: handler, manager, processor. It plays an important role in the process of message transmission. It is the main processor of messages. To put it bluntly, it is to receive messages and finally process messages (it is like a courier, receive express, then receive express bills and send express).

1. Constructor of Handler (API 28)

Handler()
Handler(Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper)
Handler(Looper looper, Callback callback)
Handler(Looper looper, Callback callback, boolean async)

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

It can be found from the source code that the above construction methods are called down one by one. The first calls the second, the second calls the third... Therefore, we first focus on the last method:

	public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;				
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

This is a construction method of assignment. Look at another construction method:

	public Handler(Callback callback, boolean async) {
        //......
        mLooper = Looper.myLooper();  //Returns the Looper object associated with the current thread, which will be described later
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;  //Returns the message queue of the Looper object, which will be described later in the MessageQueue
        mCallback = callback;  //Interface callback
        mAsynchronous = async; //Asynchronous
    }
public interface Callback {
    public boolean handleMessage(Message msg); //We are all familiar with this function. I won't go into detail for the moment. In short, we all know that it is used to callback messages
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

The following things will be established throughout the construction method:

  • Get the Looper object of the thread where the current Handler instance is located: mloop = Looper myLooper()
  • If Looper is not empty, get the message queue of Looper and assign it to the member variable mqueue of Handler: mqueue = mloop mQueue
  • Callback can be set to handle message callbacks: mCallback = callback

The Handler is the message Handler, but it is not the big man who finally processes the message. It has and can only have one superior and one leader, namely Looper, The Handler reports the message to the Looper (leader), and then waits in line. When the Looper (leader) processes the message, it will notify the Handler to get the message and assign tasks to the Handler. After the Handler gets the message, it will distribute it down by itself. The Handler can only obey the orders of the Looper (leader).

Take a practical scenario:

When you need to update the UI of the main thread in the child thread, you will create a Handler object under the current Activity, and then update the UI in its handleMessage().

	private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //...  Update UI
        }
    };

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

When you create this mHandler instance, the underlying layer does the following:

  • 1. Get the Looper of the thread where the mHandler is located. The current mHandler is created in the Activity. Obviously, the current thread is the main thread, so the member variable of mHandler mloop = Looper Myloop(), the current main thread Looper has been assigned here.
  • 2. Then, judge whether mLooper is empty. Obviously, it is not empty, so the message queue of the main thread will be assigned to mQueue. Tell the Handler that if you have a message, send it to the message queue. I (Looper) will process it one by one. After processing, I will tell you and you can process it again.
From this, we can conclude that:
1. Handler has and can only bind the Looper of one thread;
2. The message of the Handler is the message queue sent to the Looper, which needs to wait for processing;

Therefore, if you declare a Handler in the child thread, you cannot directly update the UI. You need to call the relevant constructor of the Handler and pass it into the Looper of the main thread. In this way, you can update the UI after creating the Handler instance. In addition, it should be noted that the child thread does not open the exclusive Looper by default. Therefore, before creating a Handler in the child thread, you must first open the Looper of the child thread, otherwise an exception will pop up and then GG. You can know from the part of the construction method posted above:

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

 
  • 1
  • 2
  • 3
  • 4
  • 5

The above is the process of creating a Handler. With a Handler instance, how to deliver messages?

2. Methods related to Handler sendMessage() (API 28)

First, the previous figure shows the mutual calling relationship of sendXXXMessageXXX():

It can be seen that when we call Handler to send messages, we will eventually call the sendMessageAtTime () method and finally call enqueueMessage() to the message queue.

	public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;  //Get the current message queue
        if (queue == null) {   //If Looper is not specified when creating the Handler, there will be no corresponding message queue, and it will naturally be null
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis); 
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;   //This target is what we mentioned earlier
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • msg.target = this
    Before sending the message to the message queue, the target of the message is clearly specified as the current Handler, so that it can be used when distributing the message in the loop later.
  • queue.enqueueMessage(msg, uptimeMillis)
    Then the enqueueMessage () method of the message queue is invoked, and two parameters, one Message and one long type, are passed.

The above is the process of creating and sending messages by the Handler.

3. Handler dispatchMessage() method (API 28)

As mentioned earlier, the message is sent to the Looper for processing. After processing, the Handler will be notified again for processing. Then, how do you notify the Handler to process the message? The secret lies in the method dispatchMessage():

	/**
     * System messages are processed here
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
/**
 * Subclasses must implement this to receive messages
 */
public void handleMessage(Message msg) {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

After the Looper processes the Message, it will use the target of the Message, that is, the target mentioned above, that is, the Handler that sends the Message. The Looper will call the dispatcher's dispatchMessage() method to distribute the Message. Therefore, when enqueueMessage() sends the Message, why do you have to specify the target of the Message.

Return to the dispatchMessage() method:
1. First, we will judge whether the callback of the Message is empty. The callback here is the callback we mentioned earlier in the Message. When the static method creates a Message, you can specify the callback. If it is not empty, the result will be recalled to the callback;
2. The same is true if the Handler's mCallback is not empty.
3. Usually, we do not pass in this callback, but directly implement the handleMessage () method, which handles the UI update task.

The above is the basic process for the Handler to send and receive messages: send the message to the queue - > drink tea and wait - > receive the message - > distribute the message - > process it in the callback.

3, MessageQueue

As we know earlier, when the Handler sends a message, it will call the enqueueMessage() method of MessageQueue, and directly access the source code (API 28):

	boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {  //Judge the Handler of msg
            throw new IllegalArgumentException("Message must have a target.");
        }
        //......
        synchronized (this) {  //Because it is a queue, there is a sequence, so the synchronization mechanism is used
            //......
            msg.when = when;
            Message p = mMessages;  //The last Message in the column 
           //......
            if (p == null || when == 0 || when < p.when) {    
           	    //If the queue is empty, or the waiting time is 0, or the waiting time is shorter than that of the previous one, cut in the queue
                msg.next = p;  //The next here refers to the next node of the queue mentioned in the Message
                mMessages = msg;
                //......
            } else { 
                //......
                Message prev;
                for (;;) {     
                    //Here, the for loop is used to fetch an empty Message or a Message when is longer than the current Message, and then insert it
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //......
                }
                msg.next = p;       // Displacement insertion
                prev.next = msg;  // Displacement insertion
            }
		    //......
        }
        return true;
    }

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

The above is the process principle of message queue inserting messages. Messages are stored through the data structure of one-way linked list. Now that there is a method for inserting messages for the Handler to insert messages, there should be a corresponding method for fetching messages for Looper to call and fetch messages. It is the method of Message next(). The code will not be posted. Go to check it by yourself. The process is still very simple.

4, Looper

Looper plays a key role in the Handler mechanism. It is the engine for cyclic message processing, Never stop (forever moving chicken). It constantly takes messages from the message queue, processes them, and then distributes processing events. Each thread can and can only bind one looper. The reason why the main thread can process messages is that the looper loop has been started in the main() method of ActivityThread when the APP is started.
Click go to view the startup process of Looper in the main () method

Next, go directly to the source code (API 28) of the loop key method loop()

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

    for (;;) {
        Message msg = queue.next();  //Get the message from the team leader
        if (msg == null) {
            // If the message is empty, skip and proceed to the next message
            return;
        }
        //......
        try {
            msg.target.dispatchMessage(msg);
            //......
        } finally {
           //......
        }
       //......
        msg.recycleUnchecked();  //Recycle messages that may be in use
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

It can be seen that Looper's message processing cycle is quite simple, that is, take out the message, distribute it, and recycle it

summary

The Handler mechanism can be briefly described as:
Handler sends Message to the message queue of Looper, that is, MessageQueue, waiting for Looper to read Message, processing Message, then invoking Message target, that is, Handler Handler (dispatchMessage) method, callback the message to the () method, and then update the update operation.

Topics: Handler