SoFunction
Updated on 2025-04-09

Analyzing Android's message mechanism from the source code perspective

Preface

Speaking of Android's message mechanism, the main thing is to refer to the Handler's running mechanism. This includes the work process of MessageQueue and Looper.

Before starting the text, raise two questions:

  1. Why does the operation of updating the UI need to be performed in the main thread?
  2. Why won’t the main thread get stuck in the dead loop in Android?

The UI thread's judgment is done in the checkThread method in ViewRootImpl.

For the first question, here is a simple answer:

If the UI can be modified in child threads, concurrent access by multithreads may lead to unpredictability of UI controls. Using locking methods will reduce the access efficiency of UI and block the execution of other threads. Therefore, the easiest and most effective method is to use a single-threaded model to handle UI operations.

Handler's operation cannot be separated from the underlying MessageQueue and Looper support. MessageQueue translates to a message queue, which stores the Message required by Handler. MessageQueue is not a queue, but in fact, it uses a single linked list data structure to store Messages.

So how does Handler get the Message? At this time, Looper is needed. Looper starts a dead loop through(), constantly fetching messages from MessageQueue and passing them to Handler.

Another knowledge point here is the acquisition of Looper. Here we need to improve a storage class: ThreadLocal

How ThreadLocal works

ThreadLocal is a data storage class inside a thread. It can store data in a certain thread, and other threads cannot obtain the data of that thread. Let’s look at whether this view is correct through principle.

 public void set(T value) {
  Thread t = ();
  ThreadLocalMap map = getMap(t);
  if (map != null)
   (this, value);
  else
   createMap(t, value);
 }
 
  public T get() {
  Thread t = ();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    e = (this);
   if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T);
    return result;
   }
  }
  return setInitialValue();
 }

It can be seen that its set and get methods are the operations done in the current thread. ThreadLocalMap is an array table inside. This ensures that the data in different threads does not interfere with each other.

In addition to using the use of Looper in the Handler, ThreadLocal is also used in some complex scenarios, such as the delivery of listeners.

We have a brief understanding of ThreadLocal, so we will sort out the message mechanism step by step from New Handler().

How Looper works

// 

 public Handler() {
  this(null, false);
 }
 // callback message callback; whether async is synchronized public Handler(Callback callback, boolean async) {
  ...
  // 1. First get looper  mLooper = ();
  if (mLooper == null) {
   throw new RuntimeException(
    "Can't create handler inside thread " + ()
      + " that has not called ()");
  }
  // 2. Get MessggeQueue  mQueue = ;
  mCallback = callback;
  mAsynchronous = async;
 }

We usually use the method without parameters, which passes in empty callbacks and false.

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

Here is the ThreadLoacal class we mentioned earlier. So when is the looper value set in?

Its setting method is actually in the prepare method and prepareMainLooper method. Let's take a look at it separately:

 public static void prepare() {
  prepare(true);
 }

 private static void prepare(boolean quitAllowed) {
  // Before creating a looper, determine whether the looper has been bound to threadloacal, which is also the reason why prepare can only be set once.  if (() != null) {
   throw new RuntimeException("Only one Looper may be created per thread");
  }
  (new Looper(quitAllowed));
 }

 public static void prepareMainLooper() {
  // This is actually the prepare method called  prepare(false);
  synchronized () {
   if (sMainLooper != null) {
    throw new IllegalStateException("The main Looper has already been prepared.");
   }
   sMainLooper = myLooper();
  }
 }

Through the above, the prepare method can only be set once, so why can we use it directly in the main thread? The entrance to the app program is in the main method in ActivityThread:

public static void main(String[] args) {
  ...

  //1. Initialize the Looper object  ();
  
  // 2. Turn on infinite loop  ();
  throw new RuntimeException("Main thread loop unexpectedly exited");
 }

I saw it, the initialization is here, so let’s take a look at the initialization method of looper:

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

Looper initialization does two things: create a message queue MessageQueue and get the current thread. At this point, we can reach a conclusion:

  • The prepare method can only be called once in a thread.
  • Looper initialization can only be called once in a thread.
  • Finally, we can know that a thread corresponds to a Looper, and a Looper corresponds to a MessageQueue.

Looper can be understood as a factory line, constantly taking Message from MessageQueue, the way to open the factory line is ()

 public static void loop() {
  final Looper me = myLooper();
  // 1. Determine whether the looper exists  if (me == null) {
   throw new RuntimeException("No Looper; () wasn't called on this thread.");
  }
  final MessageQueue queue = ;
  ...
  
  //2. Open a dead loop  for (;;) {
   Message msg = (); // might block
   if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
   }
   ...
   try {
    (msg);
    dispatchEnd = needEndTime ? () : 0;
   } finally {
    if (traceTag != 0) {
     (traceTag);
    }
   }
  ...
  }
 }

The looper method continuously retrieves Message messages from the MessageQueue by opening a dead loop. When the message is empty, exit the loop. Otherwise, the (msg) method is called, and the target is the Handler object bound by msg.

How Handler works

Well, here we go back to the Handler class.

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

This handleMessage is the method we need to implement. So how is the Handler set into the Message? Let's take a look at the sendMessage method we are familiar with:

 public final boolean sendMessage(Message msg)
 {
  return sendMessageDelayed(msg, 0);
 }
 
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
 {
  if (delayMillis < 0) {
   delayMillis = 0;
  }
  return sendMessageAtTime(msg, () + delayMillis);
 }
 
  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  ...
  return enqueueMessage(queue, msg, uptimeMillis);
 }
 
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  // The key code is here!   = this;
  if (mAsynchronous) {
   (true);
  }
  return (msg, uptimeMillis);
 }

It can be seen that through a series of methods, the handler is assigned to the target of msg in the enqueueMessage. The last call is the enqueueMessage method of MessageQueue:

 boolean enqueueMessage(Message msg, long when) {
  if ( == null) {
   throw new IllegalArgumentException("Message must have a target.");
  }
  if (()) {
   throw new IllegalStateException(msg + " This message is already in use.");
  }

  synchronized (this) {
   if (mQuitting) {
    IllegalStateException e = new IllegalStateException(
       + " sending message to a Handler on a dead thread");
    (TAG, (), e);
    ();
    return false;
   }

   ();
    = when;
   Message p = mMessages;
   boolean needWake;
   if (p == null || when == 0 || when < ) {
    // New head, wake up the event queue if blocked.
     = p;
    mMessages = msg;
    needWake = mBlocked;
   } else {
    needWake = mBlocked &&  == null && ();
    Message prev;
    for (;;) {
     prev = p;
     p = ;
     if (p == null || when < ) {
      break;
     }
     if (needWake && ()) {
      needWake = false;
     }
    }
     = p; // invariant: p == 
     = msg;
   }

   // We can assume mPtr != 0 because mQuitting is false.
   if (needWake) {
    nativeWake(mPtr);
   }
  }
  return true;
 }

The enqueueMessage method mainly does two things:

First, determine whether the handler exists and is in use. Then insert into the MessageQueue in chronological order.

The basic process has been sorted out here. Go back to our initial question: () is a dead loop, why doesn’t the main thread be blocked?

Let's take a look at the next method of MessageQueue:

 Message next() {
  final long ptr = mPtr;
  if (ptr == 0) {
   return null;
  }

  int pendingIdleHandlerCount = -1; // -1 only during first iteration
  int nextPollTimeoutMillis = 0;
  for (;;) {

   nativePollOnce(ptr, nextPollTimeoutMillis);
   ...
  }
 }
 

The nativePollOnce method is a native method. When this native method is called, the main thread will release CPU resources and enter the sleep state until the next message arrives or a transaction occurs. The main thread is awakened by writing data to the pipe write end. The epoll mechanism is used here. For detailed analysis of nativePollOnce, please refer to:nativePollOnce function analysis

Summarize

  1. The app program starts from the main method in ActivityThread, and creates Looper and MessageQueue and binds between ThreadLocal and thread through().
  2. When we create a Handler, we use ThreadLocal to get the Looper in that thread and the MessageQueue bound to the Looper.
  3. Use the() method to bind msg and Handler, and then insert msg into the MessageQueue in chronological order.
  4. After the main thread is created, () starts a (not taking up resources) dead loop, continuously retrieves the Message from the existing MessageQueue of Looper, and then calls the dispatchMessage(msg) method of the Handler bound to the Message that is not empty, and finally calls the handlerMessage method we rewrite.

References

Exploration of Android development art

The above is the detailed content of analyzing Android's message mechanism from the source code perspective. For more information about Android's message mechanism, please follow my other related articles!