The way to advanced Android [4] read the Handler mechanism

preface

Android development is certainly inseparable from dealing with Handler. It is usually used as a communication tool between main thread and sub thread, and Handler, as an important member of message mechanism in Android, does bring great convenience to our development.

It can be said that as long as there is an asynchronous thread communicating with the main thread, there must be a Handler.

So, what is the principle behind the Handler's communication mechanism?

This article will show you.

Note: the system source code shown in this article is based on Android-27 and has been deleted.

1. Re recognize Handler

We can use Handler to send and process messages and Runnable associated with a thread. (Note: Runnable is encapsulated in a Message, so it is essentially a Message  )

Each Handler is bound to a thread and associated with the thread's MessageQueue, so as to realize message management and inter thread communication.

1.1 basic usage of handler

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    //Messages are accepted and processed here
  }
};
//send message
handler.sendMessage(message);
handler.post(runnable);

Instantiate a Handler and override handleMessage   Method, and then call its send when needed   And post   A series of methods can be used, which is very simple and easy to use, and supports delayed messages. (for more methods, please consult the API documentation)

But it's strange that we didn't see any MessageQueue, nor did we see its logic bound to threads. What's going on?

2. Principle analysis of handler

I believe you have heard about Looper and MessageQueue for a long time, so I won't beat around the bush.

However, before starting to analyze the principle, clarify our problem:

  1. How are handlers associated with threads?
  2. Who manages the messages sent by the Handler?
  3. How does the message return to the handleMessage() method?
  4. What's the matter with thread switching?

two point one   Association between Handler and Looper

In fact, when we instantiate the Handler, the Handler will check whether the Looper of the current thread exists. If it does not exist, an exception will be reported, that is, the Looper must be created before creating the Handler.

The code is as follows:

public Handler(Callback callback, boolean async) {
        //Check whether the current thread has a Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //Looper holds a MessageQueue
        mQueue = mLooper.mQueue;
}

I believe many students have encountered this exception, but we usually use it directly. We can't feel this exception because the main thread has created a Looper for us. Remember first, we'll talk about it later.

A complete example of Handler usage is as follows:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

Looper.prepare() :

//Looper
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));
}

Looper provides Looper.prepare()   Method to create a looper, and the binding function with the current thread will be realized with the help of ThreadLocal. Looper.loop() will continue to try to obtain messages from MessageQueue and distribute them to the corresponding Handler (see [2.3]).

In other words, the association between Handler and thread is realized by Looper.

2.2 Message storage and management

The Handler provides some column methods for us to send messages, such as send() series and post() series.

However, no matter what method we call, we will eventually go to MessageQueue.enqueueMessage(Message,long)   method.

With sendEmptyMessage(int)   For example:

//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
  			-> queue.enqueueMessage(Message, long);

Here, the manager of the message   MessageQueue is exposed.
Gu Ming thinks that MessageQueue is a queue, which is responsible for incoming and outgoing messages.

2.3 Message distribution and processing

After understanding the sending and storage management of Message, it is time to lift the veil of distribution and processing.

I mentioned Looper.loop()   Be responsible for the distribution of messages and analyze them in this chapter.

Let's first look at the methods involved:

//Looper
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;
    //...
    for (;;) {
       // Keep getting messages from MessageQueue
        Message msg = queue.next(); // might block
        //Exit Looper 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //...
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            //...
        }
        //...
				//Recover message, see [ 3.5 ]
        msg.recycleUnchecked();
    }
}

MessageQueue.next() is called in loop():

//MessageQueue
Message next() {
    //...
    for (;;) {
        //...
        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;
            //...
            if (msg != null) {
                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;
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
        }

        // Run the idle handlers. Learn about IdleHandler yourself
        //...
    }
}

Also called   msg.target.dispatchMessage(msg)  , Msg.target is the Handler that sent the message, so it is called back to the Handler:

//Handler
public void dispatchMessage(Message msg) {
  //msg.callback is Runnable. If it is a post method, this if will be followed
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //For callback, see [3.4]
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    //Callback to Handler's handleMessage method
    handleMessage(msg);
  }
}

Note: the dispatchMessage() method does special processing for the runnable method. If so, it will be executed directly   Runnable.run()  .

Analysis: loop. Loop () is an endless loop. It will continuously call MessageQueue.next() to get the Message and call msg.target.dispatchMessage(msg)   Back to the Handler to distribute the Message, so as to complete the callback of the Message.

Note: the loop() method does not block the main thread. See [6].

So what about thread switching?
Many people do not understand this principle, but it is actually very simple. We draw the involved method call stack as follows:

Thread.foo(){
	Looper.loop()
	 -> MessageQueue.next()
 	  -> Message.target.dispatchMessage()
 	   -> Handler.handleMessage()
}

Obviously, the thread of Handler.handleMessage() is ultimately determined by the thread calling loop. Loop ().

We usually send messages from the asynchronous thread to the Handler. The Handler's handleMessage() method is called in the main thread, so the message is switched from the asynchronous thread to the main thread.

2.3 schematic principle

The principle analysis of the text version is over here. If you still don't understand it here, it doesn't matter. I specially prepared some pictures for you. With the previous chapters, you can read them a few more times.

2.4 summary

Behind the Handler is the assistance of Looper and MessageQueue, which work together and have a clear division of labor.

Try to summarize their responsibilities as follows:

  • Looper: responsible for the associated thread and Message distribution. Under this thread * * get the Message from the MessageQueue and distribute it to the Handler;
  • MessageQueue: it is a queue that is responsible for Message storage and management and managing messages sent by the Handler;
  • Handler: it is responsible for sending and processing messages, provides API s for developers, and hides the details of the implementation behind it.

Summarize the questions raised in chapter [2] in one sentence:

Messages sent by the Handler are stored and managed by the MessageQueue, and Loopler is responsible for callback messages to handleMessage().

The conversion of threads is completed by Looper, and the thread where handleMessage() is located is determined by the thread where Looper.loop() caller is located.

3. Extension of handler

Although the Handler is simple and easy to use, you still need to pay attention to it. In addition, there are some little-known knowledge and skills related to the Handler, such as IdleHandler.

Due to the characteristics of Handler, it is widely used in Android, such as AsyncTask, HandlerThread, Messenger, IdleHandler, IntentService and so on.

I'll explain some of these. I can search for relevant content to understand what I haven't mentioned.

3.1 causes of memory leakage caused by handler and the best solution

The Handler allows us to send a delay message. If the user closes the Activity during the delay, the Activity will be disclosed.

This disclosure is because the Message will hold the Handler, and because of the characteristics of Java, the internal class will hold the external class, so that the Activity will be held by the Handler, which will eventually lead to the disclosure of the Activity.

The most effective way to solve this problem is to define the Handler as a static internal class, hold the weak reference of the Activity internally, and remove all messages in time.

The example code is as follows:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

And remove the message before Activity.onDestroy(), adding a layer of guarantee:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}

This double guarantee can completely avoid memory leakage.

Note: simply removing messages in onDestroy is not safe because   onDestroy does not necessarily execute.

3.2 why can we use Handler directly in the main thread without creating Looper?

We mentioned earlier that each Handler thread has a Looper, and the main thread is no exception, but we have not prepared the Looper of the main thread and can use it directly. Why?

Note: we usually think of ActivityThread as the main thread. In fact, it is not a thread, but the manager of the main thread operation, so I think the ActivityThread   It is understandable that the main thread is the main thread. In addition, the main thread can also be called UI thread.

The ActivityThread.main() method has the following code:

//android.app.ActivityThread
public static void main(String[] args) {
  //...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

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

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

Looper.prepareMainLooper(); The code is as follows:

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

You can see that the Looper.prepareMainLooper() method is called in the ActivityThread, the Looper of the main thread is created, and the loop() method is called, so we can directly use the Handler.

Note: loop. Loop ()   It is an endless loop, and the following code will not be executed under normal conditions.

3.3 Looper of main thread is not allowed to exit

If you try to exit Looper, you will get the following error message:

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)

why? In fact, the reason is very simple. The main thread is not allowed to exit, which means that the APP needs to hang up.

3.4 what can the Callback hidden in the handler do?

In the Handler's construction method, there are several requests to pass in a Callback. What is it and what can it do?

Let's take a look at Handler.dispatchMessage(msg)   method:

public void dispatchMessage(Message msg) {
  //The callback here is Runnable
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //If callback processes the msg and returns true, handleMessage will not be called back
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

You can see that Handler.Callback has the right to process messages first  , When a message is processed and intercepted by callback (returns true), the Handler's handleMessage(msg)   Method will not be called; If callback   If the message is processed but not intercepted, it means that a message can be processed by callback and Handler at the same time.

This is very interesting. What does it do?

We can use the Callback interception mechanism to intercept the Handler's message!

Scene: Hook ActivityThread.mH , there are member variables mH in ActivityThread  , It is a Handler and an extremely important class. Almost all plug-in frameworks use this method.

3.5 the best way to create a Message instance

Because handlers are very commonly used, Android has designed a recycling mechanism for messages to save overhead. Therefore, we try to reuse messages when using them to reduce memory consumption.

There are two methods:

  1. Static method via message   Message.obtain();     obtain;
  2. Public method through Handler   handler.obtainMessage();  .

3.6 correct posture of bouncing Toast in sub thread

When we try to play Toast directly in the sub thread, we will crash:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

In essence, the implementation of Toast depends on the Handler, which can be modified according to the requirements of the sub thread to use the Handler (see [2.1]). Similarly, Dialog.

The correct example code is as follows:

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this, "It won't collapse!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

3.7 clever use of Looper mechanism

We can use Looper's mechanism to help us do some things:

  1. Send Runnable post to the main thread for execution;
  2. Use Looper to judge whether the current thread is the main thread.

The complete example code is as follows:

public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}

Can save a lot of template code.

4. Summary of knowledge points

From the above, we can get some knowledge points. Summarize them for easy memory.

  1. Behind the Handler are looper and MessageQueue. Looper is responsible for message distribution and MessageQueue is responsible for message management;
  2. Before creating a Handler, you must first create a Looper;
  3. Looper has the function of exit, but the looper of the main thread is not allowed to exit;
  4. Looper of asynchronous thread needs to call looper. Myloop(). Quit(); sign out;
  5. Runnable is encapsulated in Message, which can be said to be a special Message;
  6. Handler.handleMessage()   The thread in which the loop. Loop () method is called, or the thread in which the loop is located, is not the thread that creates the handler;
  7. Using Handler in the way of internal class may lead to memory leakage. Even if the delay message is removed in Activity.onDestroy, it must be written as a static internal class;

Article transferred from https://juejin.cn/post/6844903783139393550 , in case of infringement, please contact to delete.

Related videos:

Analysis of key questions in Android golden nine silver ten interview -- detailed analysis of Handler source code_ Beep beep beep_ bilibili
[advanced Android tutorial] - Handler source code analysis for Framework interview_ Beep beep beep_ bilibili
[advanced Android tutorial] - Analysis of hot repair Principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - how to solve OOM problems and analysis of LeakCanary principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - OkHttp principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - finally, someone can explain the design principle of retrofit clearly_ Beep beep beep_ bilibili
[advanced Android tutorial] - WorkManager principle of interview core_ Beep beep beep_ bilibili

Posted on Sun, 07 Nov 2021 22:50:48 -0500 by shutat