What is the Handler

Handler is mainly used for asynchronous message processing. After sending a message, it first enters a message queue, and the function that sent the message immediately returns. Another part of the message queue takes out the message one by one, and then processes the message. This mechanism is typically used to handle relatively long operations.

How to use Handler

public class HandlerActivity extends AppCompatActivity { private static final String TAG = "HandlerActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); testSendMessage(); } public void testSendMessage() { Handler handler = new MyHandler(this); Message message = Message.obtain(); message.obj = "test handler send message"; handler.sendMessage(message); } // Note 1: Why static internal? static class MyHandler extends Handler { WeakReference<AppCompatActivity> activityWeakReference; // Note 2: Why weak references?? public MyHandler(AppCompatActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.d(TAG, (String) msg.obj); }}}Copy the code

Handler source code how to interpret

From the use of the way of the scene, let’s step by step to explore how to achieve the inside, as well as the two points marked above, I will introduce in the back, each guest officer listen to me slowly. First, look at the relationship between the four king Kong, no matter how many words are expressed, it is not as direct as a picture.

From the above figure, we can simply see how Handler, MessageQueue, Message, and Looper hold each other. We can probably understand the Message transmission. Next, let’s start with a sequence diagram to see how the message is sent step by step.

  1. The handler. sendMessage method is entered.
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

Copy the code
  1. Then continue to call Handler. SendMessageDelayed method.
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code
  1. Go on Handler sendMessageAtTime method, it will use MessageQueue object, explain here, where is the mQueue get, is in the Handler method in the structure. MLooper = looper.myLooper () mQueue= mlooper.mqueue The MessageQueue in Handler is the MessageQueue object held in Looper.

—-> If we use a static inner class, we will print Log: The following Handler class should be static or leaks might occur. Will alert you that you may be causing a memory leak. So I used the static modifier at note 1.

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
  1. Then process on the sequence diagram, at this time to enter to MessageQueue. EnqueueMessage method, this method is the MSG object into MessageQueue queue, note here, gave the handler object assignment to MSG. Target, behind the use of, Is the key.
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); / / 3, is about to enter MessageQueue. EnqueueMessage method. }Copy the code
  1. Then to see MessageQueue. EnqueueMessage method, this method is in accordance with the time sequence is inserted into the Message in the chain table structure data objects.
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { //4. This is the same Handler object that is held by msg.target. throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { ... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; 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 {// List insert operations, not familiar with the data structure can be seen. 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; } // We can assume mPtr ! = 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); // Draw the emphasis, where to wake up the waiting next method. } } return true; }Copy the code

At this point, a message is a team entry. MessageQueue, which is a queue by name, actually uses a message. next pointer to operate on a linked list. The message is enqueued, and how it is sent will be explained later. Loop. Loop method, type the key. Some of the code is omitted, and only the core code is focused. This is done in an infinite loop, where the Message object is fetched and then the Handler object held by the message. target variable is called and the Handler.

public static void loop() { final Looper me = myLooper(); . final MessageQueue queue = me.mQueue; . for (;;) { Message msg = queue.next(); // might block //7. Fetch the Message object using the messagequeue.next () method. . final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked(); }}Copy the code

7-8. The messagequeue.next () method gets the Message object.

Message next() { ... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) {// infinite loop if (nextPollTimeoutMillis! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); // 5: The blocking key is avoided, the resource is released and waiting. Doubt: Being on hold, it definitely needs something to wake it up. If (needWake) {nativeWake(mPtr); // Draw the emphasis, where to wake up the waiting next method. }. synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis();  Message prevMsg = null; Message msg = mMessages; if (msg ! = null && msg.target == null) {//****** This logic is preferentially triggered during message synchronization barriers. // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) {// Find the current MSG object. if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }... nextPollTimeoutMillis = 0; }}Copy the code

9. The Handler. DispatchMessage method, here are judgment, if use the view in the Activity. The post method calls, will go to the handleCallback callback. Sending messages via sendMessagexxx goes to the handleMessage callback.

/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code

This method overrides the handleMessage method by sending the MSG object to where the client defines the Handler. At this point, the process of sending messages by the Handler is introduced.

conclusion

When the Handler sends a Message, the Handler object is added to the target property of the Message in the handler. enqueueMessage method. For the last Message. Target. DispatchMessage made a guarantee. The Message object is then placed in MessageQueue’s message.next to complete the addition of the Message list. And this MessageQueue is the object that’s held in the Looper, So you can through the stars class through the MessageQueue. The next () — – > the Message. The next () — – > the Message. The target. The dispatchMessage (MSG) completed the distribution of the Message.

Added point

How are Looper objects new?

The looper.prepareMainLooper () method is called from the main() function of the ActivityThread class of the application process, which creates the Looper from the main thread.

The looper.prepareMainLooper () method is a system call and the developer cannot call it again or it will throw an exception.

Prepare is a real new Looper. Next, look at Looper’s constructor.

This is where the MessageQueue is created, and this is where the MessageQueue in the Handler is created.

Why keep Looper in ThreadLocal?

ThreadLocal: a variable copy of a thread, isolated for each thread. ThreadLocal uses the current thread as the Key and the object that needs to be stored as the Value in the dictionary, so that when the client retrieves the Value, the current thread only retrieves a copy of the saved Value. Going back to Looper, you can see that there should be only one Looper in a thread.

Why does Message recommend using obtain() rather than New Message() to obtain Message objects?

The pooling technique is involved here: A Message pool is maintained in Message, and messages are recycled when they are used. Reduce the overhead of object creation and destruction; Thread pools in Java also use this idea.

Synchronization barrier

The synchronization barrier mechanism allows the draw message to be executed over other messages. Synchronization barriers will be used for UI drawing in the system, and are rarely used in development. The core code is to set a target=null message and insert it into the header of the message list.

AsyncHronous messages in the synchronization barrier whose message asyncHronous asyncHronous is set to true are then preferentially looked up in messagequeue.next.

Why does Handler cause memory leaks and the solution?

A memory leak caused by a Handler usually occurs when sending a delayed message. After the Activity is closed, the MessageQueue on the main thread holds a reference to the message, which holds a reference to the Handler. Handler holds a reference to the Activity as an anonymous inner class, so you have the following reference chain. To solve

  1. Using static inner class, if you want to call the method in the Activity, you can set a WeakReference activityWeakReference reference in the static inner class.
  2. At Activity destruction time, in the onDestory() method, call handler.removecallbacks to remove the runnable.