SoFunction
Updated on 2025-04-10

Analysis of the working principle of Android Handler

Introduction

In Android, only the main thread can operate the UI, but the main thread cannot perform time-consuming operations, otherwise it will block the thread and generate an ANR exception, so time-consuming operations are often placed on other subthreads. If the UI needs to be updated in the child thread, the message is generally sent through the Handler, the main thread accepts the message and performs corresponding logical processing. In addition to using Handler directly, you can also update the UI through the view's post method and the Activity's runOnUiThread method, which also utilizes Handler internally. In the previous article, Android AsyncTask source code analysis, it also mentioned that it uses Handler internally to pass the processing results of the task back to the UI thread.

This article analyzes the message processing mechanism of Android in depth and understands how Handler works.

Handler

Let’s first look at the usage of Handler through an example.

public class MainActivity extends AppCompatActivity {
  private static final int MESSAGE_TEXT_VIEW = 0;
  
  private TextView mTextView;
  private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch () {
        case MESSAGE_TEXT_VIEW:
          ("UI updated successfully");
        default:
          (msg);
      }
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    (savedInstanceState);
    setContentView(.activity_main);
    Toolbar toolbar = (Toolbar) findViewById();
    setSupportActionBar(toolbar);

    mTextView = (TextView) findViewById(.text_view);

    new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          (3000);
        } catch (InterruptedException e) {
          ();
        }
        (MESSAGE_TEXT_VIEW).sendToTarget();
      }
    }).start();

  }
}

The above code first creates a new instance of Handler and rewrites the handleMessage method. In this method, the corresponding UI update is performed according to the type of message received. Then take a look at the source code of Handler's construction method:

public Handler(Callback callback, boolean async) {
  if (FIND_POTENTIAL_LEAKS) {
    final Class<? extends Handler> klass = getClass();
    if ((() || () || ()) &&
        (() & ) == 0) {
      (TAG, "The following Handler class should be static or leaks might occur: " +
        ());
    }
  }

  mLooper = ();
  if (mLooper == null) {
    throw new RuntimeException(
      "Can't create handler inside thread that has not called ()");
  }
  mQueue = ;
  mCallback = callback;
  mAsynchronous = async;
}

In the constructor, the Looper object is obtained by calling (). If mLooper is empty, an exception will be thrown: "Can't create handler inside thread that has not called ()", which means: handler cannot be created on threads that do not call (). The above example does not call this method, but no exception is thrown. In fact, it is because the main thread has already called it for us when it is started, so you can create a Handler directly. If it is in other child threads, creating a Handler directly will cause the application to crash.

After getting the Handler, its internal variable mQueue is obtained, which is the MessageQueue object, which is the message queue, which is used to save the message sent by the Handler.

At this point, all three important roles of the Android message mechanism have appeared, namely Handler, Looper and MessageQueue. Generally, we are more exposed to Handler in code, but Looper and MessageQueue are indispensable for Handler runtime.

Looper

The previous section analyzed the construction of Handler, where the () method is called, and the following is its source code:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
  return ();
}

The code for this method is very simple, which is to obtain the Looper object from sThreadLocal. sThreadLocal is a ThreadLocal object, which means that Looper is thread-independent.

In the construction of Handler, from the thrown exception, we can see that each thread needs to call the prepare() method to obtain Looper, and continue to look at its code:

private static void prepare(boolean quitAllowed) {
  if (() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
  }
  (new Looper(quitAllowed));
}

It's also very simple, just set a Looper for sThreadLocal. However, it should be noted that if sThreadLocal has been set, an exception will be thrown, which means that a thread will only have one Looper. When creating a Looper, a message queue will be created internally:

private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = ();
}

The question now is, what does Looper look like?
Answer: Looper enables the message loop system, continuously fetches messages from the message queue MessageQueue and handes them to Handler for processing.

Why do you say this? Take a look at Looper's loop method:

public static void loop() {
  final Looper me = myLooper();
  if (me == null) {
    throw new RuntimeException("No Looper; () wasn't called on this thread.");
  }
  final MessageQueue queue = ;

  // Make sure the identity of this thread is that of the local process,
  // and keep track of what that identity token actually is.
  ();
  final long ident = ();
  
  //Infinite loop  for (;;) {
    Message msg = (); // might block
    if (msg == null) {
      // No message indicates that the message queue is quitting.
      return;
    }

    // This must be in a local variable, in case a UI event sets the logger
    Printer logging = ;
    if (logging != null) {
      ("&gt;&gt;&gt;&gt;&gt; Dispatching to " +  + " " +
           + ": " + );
    }

    (msg);

    if (logging != null) {
      ("&lt;&lt;&lt;&lt;&lt; Finished to " +  + " " + );
    }

    // Make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    final long newIdent = ();
    if (ident != newIdent) {
      (TAG, "Thread identity changed from 0x"+ (ident) + " to 0x"+ (newIdent) + " while dispatching to "+ ().getName() + " "+  + " what=" + );
    }

    ();
  }
}

The code of this method is a bit long, so you don't follow the details, just look at the overall logic. It can be seen that there is a dead loop inside this method, where the next message is obtained through the next() method of MessageQueue. If it is not retrieved, it will block. If a new message is successfully obtained, (msg) is called, which is a Handler object (see in the next section), and dispatchMessage is to distribute the message (it is already running in the UI thread at this time). The message sending and processing flow are analyzed below.

Message sending and processing

When the child thread sends a message, a series of sendMessage, sendMessageDelayed, and sendMessageAtTime are called. In the end, sendMessageAtTime (Message msg, long uptimeMillis) will be called. The code is as follows:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  if (queue == null) {
    RuntimeException e = new RuntimeException(
        this + " sendMessageAtTime() called with no mQueue");
    ("Looper", (), e);
    return false;
  }
  return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
   = this;
  if (mAsynchronous) {
    (true);
  }
  return (msg, uptimeMillis);
}

This method is to call enqueueMessage to insert a message into the message queue. In the enqueueMessage total, it will be set to the current Handler object.

After the message is inserted into the message queue, Looper is responsible for taking it out of the queue and then calling the Handler's dispatchMessage method. Next, let’s see how this method handles messages:

public void dispatchMessage(Message msg) {
  if ( != null) {
    handleCallback(msg);
  } else {
    if (mCallback != null) {
      if ((msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

First, if the callback of the message is not empty, handleCallback is called to process. Otherwise, determine whether the mCallback of Handler is empty. If it is not empty, call its handleMessage method. If it is still empty, the handleMessage of the Handler itself is called, which is the method we override when creating the Handler.

If the post(Runnable r) method of Handler is called when sending a message, the Runnable will be encapsulated into the callback of the message object, and then sendMessageDelayed is called. The relevant code is as follows:

public final boolean post(Runnable r)
{
  return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
  Message m = ();
   = r;
  return m;
}

At this time, handleCallback will be called in dispatchMessage for processing:

 private static void handleCallback(Message message) {
  ();
}

You can see that the run method is called directly to process the message.

If a Callback object is provided directly when creating a Handler, the message will be handed over to the handleMessage method of this object. Callback is an interface inside the Handler:

public interface Callback {
  public boolean handleMessage(Message msg);
}

The above is the process of message sending and processing. It is in the child thread when sent, but the dispatchMessage method runs in the main thread when processing.

Summarize

At this point, the analysis of the principle of Android message processing mechanism has ended. Now we can know that message processing is done through Handler, Looper and MessageQueue. The Handler is responsible for sending and processing messages. Looper creates a message queue and continuously retrieves messages from the queue and handes them to the Handler. MessageQueue is used to save messages.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.