English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Detailed Explanation and Example Code of Android Message Mechanism

Android Message Mechanism

1.Summary

When an Android application starts, it will have a default main thread (UI thread), in which a message queue (MessageQueue) is associated. All operations will be encapsulated into the message queue and then handed over to the main thread for processing. To ensure that the main thread does not exit, the operations of the message queue are placed in a dead loop, making the program equivalent to an endless execution of a dead loop. With each loop iteration, a message is taken out from the internal message queue, and the corresponding message handling function (handlerMessage) is called. After completing a message, the loop continues. If the message queue is empty, the thread will block and wait. Therefore, it will not exit. As shown in the following figure:

What is the relationship between Handler, Looper, and Message?

In the sub-thread, many operations take a long time to complete, and often need to update the UI. The most commonly used method is to post a message to the UI thread through the Handler, and then handle it in the handlerMessage method of the Handler. Each Handler is associated with a message queue (MessageQueue), and the Looper is responsible for creating a MessageQueue. Each Looper is associated with a thread (Looper encapsulates it through ThreadLocal). By default, there is only one MessageQueue, that is, the message queue of the main thread.

That is the basic principle of Android's message mechanism. If you want to know more, we start from the source code.

2.Source code analysis

}}1)Start the message loop Looper in the ActivityThread main thread

public final class ActivityThread {
  public static void main(String[] args) {
    //Code is omitted
    //1.Create the Looper for the message loop
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    //2.Execute the message loop
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
  }
}

ActivityThread creates the message queue for the main thread through Looper.prepareMainLooper(), and finally executes Looper.loop() to start the message queue. The Handler associates the message queue with the thread.

}}2Handler associates the message queue with the thread

public Handler(Callback callback, boolean async) {
    //Code is omitted
    //Get Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()"
    }
    //Get the message queue
    mQueue = mLooper.mQueue;
  }

Handler internally acquires the Looper object through the Looper.getLooper() method, associates with it, and retrieves the message queue. So how does Looper.getLooper() work?

  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }
  public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
  }
  public static void prepare() {
    prepare(true);
  }
  //Set a Looper for the current thread
  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));
  }
  //Set the Looper of the UI thread
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }

In the Looper class, the myLooper() method is obtained through sThreadLocal.get(), and the prepare() method is called in prepareMainLooper(), where a Looper object is created and the object is set to sThreadLocal(). This way, the queue is associated with the thread. The sThreadLocal.get() method ensures that different threads cannot access each other's message queues.

Why must the Handler that updates the UI be created in the main thread?

Because the Handler needs to be associated with the message queue of the main thread, so that handlerMessage can be executed in the UI thread, at this time the UI thread is safe.

}}3Message loop, message processing

The establishment of the message loop is through the Looper.loop() method. The source code is as follows:

/**
   * Run the message queue in this thread. Be sure to call
   * {@link #quit()} to end the loop.
   */
  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //1.Get the message queue
    final MessageQueue queue = me.mQueue;
    //2.Dead loop, that is, message loop
    for (;;) {
      //3.Get the message, may block
      Message msg = queue.next(); // might block
      if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
      }
      //4.Process the message
      msg.target.dispatchMessage(msg);
      //Recycle the message
      msg.recycleUnchecked();
    }
  }

From the above program, we can see that the essence of the loop() method is to establish a dead loop, and then extract messages one by one from the message queue, and finally process the messages. For Looper: create a Looper object (the message queue is encapsulated in the Looper object) through Looper.prepare(), and then store it in sThreadLocal, and then loop through messages through Looper.loop(). These two steps usually appear together.

public final class Message implements Parcelable {
  //Target processing
  Handler target; 
  //Callback of type Runnable
  Runnable callback;
  //The next message, the message queue is stored in a linked list.
  Message next;
}

It can be seen from the source code that 'target' is of Handler type. In fact, it is just a round trip, sending messages to the message queue through Handler, and then the message queue distributes messages to Handler for processing. In the Handle class:

//Message handling function, subclass overrides
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
    message.callback.run();
  }
//Message distribution
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

From the above program, it can be seen that dispatchMessage is just a dispatch method. If the callback of type Runnable is empty, then handleMessage is executed to process the message. This method is empty, so we will write the code to update the UI in this function; if the callback is not empty, then handleCallback is executed to process, which will call the run method of the callback. In fact, this is the two types of distribution of Handler, for example, if post(Runnable callback) is used, then callback is not empty. When we use Handler to sendMessage, we usually do not set callback, so handleMessage is executed.

 public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);
  }
  public String getMessageName(Message message) {
    if (message.callback != null) {
      return message.callback.getClass().getName();
    }
    return "0x" + Integer.toHexString(message.what);
  }
  public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {}}
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis()) + delayMillis);
public boolean sendMessageAtTime(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);
  }

From the above program, we can see that when post(Runnable r) is called, it will wrap the Runnable into a Message object, set the Runnable object to the callback of the Message object, and finally insert the object into the message queue. The implementation of sendMessage is similar:

public final boolean sendMessage(Message msg)
  {
    return sendMessageDelayed(msg, 0);
  }

Whether it is posting a Runnable or a Message, it will call the sendMessageDelayed(msg, time) method. The Handler ultimately appends the message to the MessageQueue, and the Looper continuously reads messages from the MessageQueue, calling the Handler's dispatchMessage to distribute messages. In this way, messages are continuously generated, added to the MessageQueue, and processed by the Handler, making the Android application run.

3.Verify

new Thread(){
  Handler handler = null;
  public void run () {
    handler = new Handler();
  ;
.start();

Is there a problem with the above code?

The Looper object is ThreadLocal, meaning each thread uses its own Looper, which can be empty. However, when creating a Handler object in a sub-thread, if the Looper is empty, an exception will occur.

public Handler(Callback callback, boolean async) {
    //Code is omitted
    //Get Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()"
    }
    //Get the message queue
    mQueue = mLooper.mQueue;
  }

When mLooper is empty, an exception is thrown. This is because the Looper object has not been created, so sThreadLocal.get() will return null. The basic principle of Handler is to establish a connection with MessageQueue and deliver messages to MessageQueue. If there is no MessageQueue, there is no need for Handler to exist. Since MessageQueue is enclosed in Looper, Looper must not be empty when creating Handler. The solution is as follows:

new Thread(){
  Handler handler = null;
  public void run () {
    //Create a Looper for the current thread and bind it to ThreadLocal
    Looper.prepare();
    handler = new Handler();
    //Start the message loop
    Looper.loop();
  ;
.start();

If you only create a Looper without starting the message loop, although no exceptions are thrown, post or sendMessage() through handler will not be effective either. Because although the message will be appended to the message queue, the message loop has not been started, so messages will not be retrieved from the message queue and executed.

Thank you for reading, and I hope it can help everyone. Thank you for your support of this site!