The foreword 0.

Do Android development must be inseparable from dealing with Handler, it is usually used by us to master thread and child thread communication tools, and Handler as an important part of the message mechanism in Android has indeed brought great convenience to our development.

You can say that wherever there is an asynchronous thread communicating with the main thread, there must be a Handler.

So, what’s behind the Handler communication mechanism?

In this article, you’ll find out.

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

1. Recognition Handler

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

Each Handler is bound to a thread and associated with the thread’s MessageQueue to manage messages and communicate between threads.

1.1 Basic Usage of Handler

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    // Here the message is accepted and processed}};// Send the message
handler.sendMessage(message);
handler.post(runnable);
Copy the code

Instantiating a Handler that overwrites the handleMessage method and then calls its Send and post methods as needed is easy to use and supports delayed messages. (See the API documentation for more information)

But we don’t see any MessageQueue or thread binding logic. Why?

2. Handler principle analysis

I’m sure you’ve heard of Looper and MessageQueue, so I won’t beat around the bush.

But before we start analyzing the principles, let’s clarify our question:

  1. How is Handler associated with threads?
  2. Who manages the messages sent by the Handler?
  3. How does the message get back to the handleMessage() method?
  4. What about thread switching?

2.1 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 not, an exception will be raised. In other words, Looper must be created before the Handler is created.

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;
}
Copy the code

I believe that many students have encountered this exception, but we usually directly use this exception because the main thread has created a Looper for us, remember first, later will talk about. (See [3.2])

A complete example of using Handler would look like this:

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

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));
}
Copy the code

Looper provides looper.prepare () to create Looper, and binds to the current thread using ThreadLocal. Looper.loop() starts trying to get messages from the MessageQueue and distribute them to the Handler (see [2.3]).

This means that the Handler is associated with the thread by Looper.

2.2 Storage and management of Messages

The Handler provides a series of methods to send messages, such as the send() series and post() series.

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

Take the sendEmptyMessage(int) method for example:

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

At this point, the manager of the message, the MessageQueue, comes to the surface. A MessageQueue is a queue that takes messages in and out of the queue.

2.3 Message distribution and processing

With a clear understanding of Message delivery and storage management, it’s time to lift the veil on distribution and processing.

As mentioned earlier, looper.loop () is responsible for message distribution, which is analyzed in this section.

Let’s first look at the methods involved:

//Looper
public static void loop(a) {
    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 (;;) {
       // Obtain messages from MessageQueue continuously
        Message msg = queue.next(); // might block
        / / out of stars
        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 {
            / /...
        }
        / /...
				// Recycle message, see [3.5]msg.recycleUnchecked(); }}Copy the code

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

//MessageQueue
Message next(a) {
    / /...
    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;
                    returnmsg; }}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
        / /...}}Copy the code

Also called MSG. Target. DispatchMessage (MSG), MSG. The target is to send the message Handler, and went back to the Handler there like this:

//Handler
public void dispatchMessage(Message msg) {
  // MSG. Callback is a Runnable, and if it is a post method, it will use this if
  if(msg.callback ! =null) {
    handleCallback(msg);
  } else {
    //callback see [3.4]
    if(mCallback ! =null) {
      if (mCallback.handleMessage(msg)) {
        return; }}// Call back to the handleMessage method of the HandlerhandleMessage(msg); }}Copy the code

Note: The dispatchMessage() method takes special care of Runnable methods, and if it does, it executes directlyRunnable.run() 。

Analysis: Stars. The loop () is an infinite loop, will continue to call MessageQueue. The next () to get the Message, and invokes the MSG. Target. DispatchMessage (MSG) back to the Handler to distribute news, To complete the message callback.

Note: The loop() method does not get stuck on the main thread, see [6].

What about thread switching? Many people don’t understand this principle, but it is very simple. Let’s draw the method call stack involved as follows:

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

Obviously, the thread on which handler.handleMessage () resides is ultimately determined by the thread that called looper.loop ().

This Handler’s handleMessage() method is called on the main thread, so the message is switched from the asynchronous thread to the main thread.

2.3 Schematic Principle

Text version of the principle of the analysis to the end here, if you see here or do not understand, it does not matter, I specially prepared for you some pictures, with the previous several chapters, and then read several times, you can understand.


2.4 summary

The Handler is assisted by Looper and MessageQueue. The three work together with clear division of labor.

Try to summarize their duties as follows:

  • Looper: responsible for the associated thread and Message distribution ** Get Message from MessageQueue and distribute to Handler;
  • MessageQueue: a queue that is responsible for the storage and management of messages. It is responsible for the management of messages sent by the Handler.
  • Handler: sends and processes messages, presents the API to developers, and hides the implementation details behind it.

The questions raised in chapter [2] can be summarized in one sentence:

Messages sent by the Handler are managed by the MessageQueue store, and it is the Loopler’s responsibility to call back messages to handleMessage().

The thread conversion is done by Looper, and the thread of handleMessage() is determined by the thread of looper.loop () caller.

3. Extension of Handler

Although Handler is simple and easy to use, but to use it or need to pay attention to a point, in addition to Handler related to some little-known knowledge skills, such as IdleHandler.

Because of the Handler feature, it is widely used in Android, such as: AsyncTask, HandlerThread, Messenger, IdleHandler, IntentService and so on.

I’m going to talk a little bit about that, but if I haven’t, you can search for it.

3.1 Causes of memory leaks caused by Handler and the best solution

This Handler allows us to send delayed messages, and if the user closes the Activity during the delay, the Activity will leak.

This leak is caused by the fact that the Message holds the Handler, and because of the nature of Java, the inner class holds the outer class, which causes the Activity to be held by the Handler, and ultimately causes the Activity to leak.

The most effective way to solve this problem is to define the Handler as a static inner class that holds a weak reference to the Activity inside and removes 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); }}}Copy the code

And then remove the message before activity.onDestroy (), adding a layer of assurance:

@Override
protected void onDestroy(a) {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}
Copy the code

In this way, the double protection can completely avoid memory leaks.

Note: Simple inonDestroyRemoving messages is not safe becauseonDestroyIt doesn’t have to be enforced.

3.2 Why can we use Handler directly on the main thread without creating Looper?

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

Note: Usually we think of ActivityThread as the main thread. In fact, it’s not a thread, it’s the operator of the main thread, so I think it’s okay to think of ActivityThread as the main thread, and the main thread as the UI thread.

In the activityThread.main () method we have 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");
}
Copy the code

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(a) {
    prepare(false);
    synchronized (Looper.class) {
        if(sMainLooper ! =null) {
            throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

PrepareMainLooper () method is called in ActivityThread to create Looper for the main thread, and loop() method is called, so we can use the Handler directly.

Note:Looper.loop()Is an infinite loop, the following code would not normally execute.

3.3 Looper on the main thread cannot 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)
Copy the code

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

3.4 What do callbacks hidden in handlers do?

There are several requests in the Handler constructor to pass the Callback, so what is it and what can it do?

Take a look at the handler.dispatchMessage (MSG) method:

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

When a message is processed by the Callback and intercepted (returning true), the Handler’s handleMessage(MSG) method is not called. If the Callback handles the message but does not intercept it, that means a message can be handled by both the Callback and the Handler.

That’s interesting. What does that do?

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

Scenario: Hook activityThread.mh. ActivityThread has a member variable, mH, which is a Handler and an extremely important class. Almost all plug-in frameworks use this method.

3.5 Best way to create Message Instances

Since Handler is very commonly used, In order to save overhead, Android has designed a recycling mechanism for Message, so we try to reuse messages when using to reduce memory consumption.

There are two methods:

  1. Through the static method of MessageMessage.obtain();To obtain;
  2. Through Handler’s public methodshandler.obtainMessage(); 。

3.6 Correct posture for playing Toast in child threads

When we try to play a Toast directly in a child thread, we crash:

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

In essence, the Toast implementation relies on the Handler, which can be modified to the requirement that the child thread uses the Handler (see [2.1]), as well as the Dialog.

The following is an example of correct code:

new Thread(new Runnable() {
  @Override
  public void run(a) {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this."It's not going to crash!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();
Copy the code

3.7 Looper mechanism

We can use Looper’s mechanics to help us do a few things:

  1. Post Runnable to main thread;
  2. Use Looper to determine whether the current thread is the main thread.

The full sample code is as follows:

public final class MainThread {

    private MainThread(a) {}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(a) {
        returnLooper.myLooper() == Looper.getMainLooper(); }}Copy the code

It saves a lot of boilerplate code.

4. Summary of knowledge

Some knowledge points can be drawn from the previous text, summarized, convenient memory.

  1. The Handler is supported by Looper and MessageQueue. Looper is responsible for message distribution and MessageQueue is responsible for message management.
  2. Looper must be created before Handler is created;
  3. Looper can exit, but Looper on main thread is not allowed to exit.
  4. Looper for asynchronous threads needs to be called by themselvesLooper.myLooper().quit();Exit;
  5. Runnable is wrapped in a Message, so it’s a special Message;
  6. Handler.handleMessage()Looper.loop() is called on the same thread as Looper, not the thread that created the Handler.
  7. Using Handler as an inner class can cause memory leaks, even if removing delayed messages in Activity. OnDestroy requires a static inner class.

5. To summarize

Behind Handler’s simplicity lies a great deal of wisdom from engineers. Try to learn from them.

After reading and understanding this article, you can say that you have a very deep and comprehensive understanding of Handler, which is definitely more than enough for the interview.

6. References and recommendations

Handler what-is-the-relationship-between-looper-handler-and-messagequeue-in- Android Android 1-Handler (Java layer) Why doesn’t the main thread get stuck in Android