SoFunction
Updated on 2025-04-11

In-depth understanding of the source code of Android message loop mechanism

Android message loop mechanism source code

Preface:

Those who engage in Android do not understand the Handler message loop mechanism and are embarrassed to say that they are an Android engineer. This knowledge point is usually asked during interviews, but I believe that most coders have never read the relevant source code. At most, they can search online and read other people's article introductions. The senior sister doesn’t want to take out the universal relationship diagram to discuss.

Recently, I have found some information about communication between android threads, sorted and studied it, and made a simple example.

andriod provides Handler and Looper to meet communication between threads. For example, a child thread downloads a picture from the network. When it is downloaded, it will send a message to the main thread. This message is passed through the Handler bound to the main thread.

In Android, the threads here are divided into threads with message loops and threads without message loops. Threads with message loops generally have a Looper, which is a new concept of Android. Our main thread (UI thread) is a message loop thread. In view of this message loop mechanism, we introduce a new mechanism Handle. If we have a message loop, we have to send corresponding messages to the message loop. Custom messages generally have their own corresponding processing, message sending and clearing, and message processing. All of these are encapsulated in the Handle. Note that Handle is only for those threads with Looper, whether it is UI threads or child threads. As long as you have Looper, I can add things to your message queue and do corresponding processing.
But there is another point here, that is, as long as it is related to UI, it cannot be placed in the child thread, because the child thread cannot operate the UI and can only perform other non-UI operations such as data and system.

In Android, the threads here are divided into threads with message loops and threads without message loops. Threads with message loops generally have a Looper, which is a new concept of Android. Our main thread (UI thread) is a message loop thread. In view of this message loop mechanism, we introduce a new mechanism Handler. When we have a message loop, we have to send corresponding messages into the message loop. Custom messages generally have their own corresponding processing, sending and clearing messages, encapsulating these in the Handler. Note that the Handler is only targeting those threads with Looper, whether it is UI threads or child threads. As long as you have Looper, I can add things to your message queue and do corresponding processing.

But there is another point here, that is, as long as it is related to UI, it cannot be placed in the child thread, because the child thread cannot operate the UI and can only perform other non-UI operations such as data and system.

First, we introduce this mechanism from our usual usage methods, and then analyze it in combination with the source code.

We usually use this:

 //1. Main thread Handler handler = new MyHandler();

 //2. Non-main thread HandlerThread handlerThread = new HandlerThread("handlerThread");
 ();
 Handler handler = new Handler(());

 //Send a message (msg);

 //Receive message static class MyHandler extends Handler {
  //For non-main threads to process messages, Looper needs to be passed, the main thread has the default sMainLooper  public MyHandler(Looper looper) {
   super(looper);
  }

  @Override
  public void handleMessage(Message msg) {
   (msg);
  }
 }

So why do we execute 1 or 2 during initialization, and we only need sendMessage to process the task? The senior sister introduces it here using a non-main thread as an example. When (), a Looper and MessageQueue for the message loop are actually created. The message loop is started at the same time and the loop is passed to the Handler. This loop will take the tasks from the MessageQueue in turn to execute. If a user wants to perform a task, he only needs to call it. What he does here is to add the message to the MessaeQueue. The same is true for the main thread, except that the main thread sMainThread and sMainLooper do not require us to actively create it. The Application is created when the program starts, and we only need to create a Handler.

We mentioned several concepts here:

  • HandlerThread thread that supports message loops
  • Handler message processor
  • Looper message loop object
  • MessageQueue Message Queue
  • Message message body

The corresponding relationship is:One-to-many, that is (one) HandlerThread, Looper, MessageQueue -> (multiple) Handler, Message

Source code analysis

1. Looper

(1) Create a message loop

prepare() is used to create a Looper message loop object. The Looper object is saved by a member variable ThreadLocal.

(2) Get the message loop object

myLooper() is used to get the current message loop object. Looper objects are retrieved from the member variable ThreadLocal.

(3) Start the message loop

loop() starts the message loop. The cycle process is as follows:

Take out a Message from the MessageQueue every time

Use the Handler corresponding to Message to process Message

Processed Messages are added to the local message pool and reuse them in a circular manner.

Loop the above steps. If there is no message indicating that the message queue is stopped, exit the loop

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

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

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

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 = ();

 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) {
   (">>>>> Dispatching to " +  + " " +
      + ": " + );
  }

  (msg);

  if (logging != null) {
   ("<<<<< 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=" + );
  }

  ();
 }
}

2. Handler

(1) Send a message

Handler supports two message types, namely Runnable and Message. Therefore, sending messages provides two methods: post(Runnable r) and sendMessage(Message msg). From the source code below, we can see that Runnable assigned a callback to the Message, and it is eventually encapsulated into a Message object object. My senior sister personally believes that external calls do not use Message uniformly, and should be a threading task compatible with Java. My senior sister believes that this idea can also be learned from the normal development process. The messages sent will be enqueued into the MessageQueue queue.

(2) Processing messages

During the Looper loop process, the message is processed through dispatchMessage (Message msg). Processing process: First check whether it is a Runnable object. If so, call handleCallback(msg) for processing, and finally call it to the () method execution thread; if it is not a Runnable object, then check whether the Callback processing mechanism has been passed in the outside world. If so, use external Callback for processing; if it is neither Runnable object nor external Callback, call handleMessage(msg), which is also the most commonly overridden method in our development process.

(3) Remove message

removeCallbacksAndMessages(). The removal message is actually removed from the MessageQueue.

public void handleMessage(Message msg) {
}

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

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

public final Message obtainMessage()
{
 return (this);
}

public final boolean post(Runnable r)
{
 return sendMessageDelayed(getPostMessage(r), 0);
}

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

private static Message getPostMessage(Runnable r) {
 Message m = ();
  = r;
 return m;
}

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

public final void removeCallbacksAndMessages(Object token) {
 (this, token);
}

3. MessageQueue

(1) Message joins the queue

Message enqueueMessage(Message msg, long when). The processing process is as follows:

The Message to be enqueued is marked InUse, when assigned

If mMessages is empty or empty, or if the execution time of the Message to be enqueued is less than the mMessage link header, the Message to be enqueued is added to the link header.

If the above conditions are not met, the linked list will be polled and the appropriate position of the linked list will be inserted according to the order from low to high.

(2) Message polling

Next() removes the Message from the MessageQueue in turn

(3) Remove message

removeMessages() can remove messages, and what they do is actually remove messages from the linked list, and add the removed messages to the message pool to provide loop multiplexing.

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");
   ("MessageQueue", (), 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 {
   // Inserted within the middle of the queue. Usually we don't have to wake
   // up the event queue unless there is a barrier at the head of the queue
   // and the message is the earliest asynchronous message in the queue.
   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;
}

Message next() {
 // Return here if the message loop has already quit and been disposed.
 // This can happen if the application tries to restart a looper after quit
 // which is not supported.
 final long ptr = mPtr;
 if (ptr == 0) {
  return null;
 }

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

  nativePollOnce(ptr, nextPollTimeoutMillis);

  synchronized (this) {
   // Try to retrieve the next message. Return if found.
   final long now = ();
   Message prevMsg = null;
   Message msg = mMessages;
   if (msg != null &&  == null) {
    // Stalled by a barrier. Find the next asynchronous message in the queue.
    do {
     prevMsg = msg;
     msg = ;
    } while (msg != null && !());
   }
   if (msg != null) {
    if (now < ) {
     // Next message is not ready. Set a timeout to wake up when it is ready.
     nextPollTimeoutMillis = (int) ( - now, Integer.MAX_VALUE);
    } else {
     // Got a message.
     mBlocked = false;
     if (prevMsg != null) {
       = ;
     } else {
      mMessages = ;
     }
      = null;
     if (false) ("MessageQueue", "Returning message: " + msg);
     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;
   }

   // If first time idle, then get the number of idlers to run.
   // Idle handles only run if the queue is empty or if the first message
   // in the queue (possibly a barrier) is due to be handled in the future.
   if (pendingIdleHandlerCount < 0
     && (mMessages == null || now < )) {
    pendingIdleHandlerCount = ();
   }
   if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run. Loop and wait some more.
    mBlocked = true;
    continue;
   }

   if (mPendingIdleHandlers == null) {
    mPendingIdleHandlers = new IdleHandler[(pendingIdleHandlerCount, 4)];
   }
   mPendingIdleHandlers = (mPendingIdleHandlers);
  }

  // Run the idle handlers.
  // We only ever reach this code block during the first iteration.
  for (int i = 0; i < pendingIdleHandlerCount; i++) {
   final IdleHandler idler = mPendingIdleHandlers[i];
   mPendingIdleHandlers[i] = null; // release the reference to the handler

   boolean keep = false;
   try {
    keep = ();
   } catch (Throwable t) {
    ("MessageQueue", "IdleHandler threw exception", t);
   }

   if (!keep) {
    synchronized (this) {
     (idler);
    }
   }
  }

  // Reset the idle handler count to 0 so we do not run them again.
  pendingIdleHandlerCount = 0;

  // While calling an idle handler, a new message could have been delivered
  // so go back and look again for a pending message without waiting.
  nextPollTimeoutMillis = 0;
 }
}

void removeMessages(Handler h, int what, Object object) {
 if (h == null) {
  return;
 }

 synchronized (this) {
  Message p = mMessages;

  // Remove all messages at front.
  while (p != null &&  == h &&  == what
    && (object == null ||  == object)) {
   Message n = ;
   mMessages = n;
   ();
   p = n;
  }

  // Remove all messages after front.
  while (p != null) {
   Message n = ;
   if (n != null) {
    if ( == h &&  == what
     && (object == null ||  == object)) {
     Message nn = ;
     ();
      = nn;
     continue;
    }
   }
   p = n;
  }
 }
}

4. Message

(1) Message creation

() Create a message. If the message pool linked list sPool is not empty, then get the first one from sPool, flags is marked UnInUse, and remove it from sPool, sPoolSize is reduced by 1; if the message pool linked list sPool is empty, then new Message()

(2) Message release

recycle() releases the message. From the internal implementation of recycleUnchecked(), we can see that flags are marked as InUse, and various other states are cleared. At the same time, Message is added to sPool, and sPoolSize is added to 1.

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
 synchronized (sPoolSync) {
  if (sPool != null) {
   Message m = sPool;
   sPool = ;
    = null;
    = 0; // clear in-use flag
   sPoolSize--;
   return m;
  }
 }
 return new Message();
}

/**
 * Return a Message instance to the global pool.
 * <p>
 * You MUST NOT touch the Message after calling this function because it has
 * effectively been freed. It is an error to recycle a message that is currently
 * enqueued or that is in the process of being delivered to a Handler.
 * </p>
 */
public void recycle() {
 if (isInUse()) {
  if (gCheckRecycle) {
   throw new IllegalStateException("This message cannot be recycled because it "
     + "is still in use.");
  }
  return;
 }
 recycleUnchecked();
}

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
 // Mark the message as in use while it remains in the recycled object pool.
 // Clear out all other details.
 flags = FLAG_IN_USE;
 what = 0;
 arg1 = 0;
 arg2 = 0;
 obj = null;
 replyTo = null;
 sendingUid = -1;
 when = 0;
 target = null;
 callback = null;
 data = null;

 synchronized (sPoolSync) {
  if (sPoolSize < MAX_POOL_SIZE) {
   next = sPool;
   sPool = this;
   sPoolSize++;
  }
 }
}

5. HandlerThread

Since Thread in Java does not have a message loop mechanism, the run() method is executed and the thread ends. HandlerThread implements a message loop by using Looper. As long as the HandlerThread or Looper's quit() method is not actively called, the loop will continue.

public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;

public HandlerThread(String name) {
 super(name);
 mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

@Override
public void run() {
 mTid = ();
 ();
 synchronized (this) {
  mLooper = ();
  notifyAll();
 }
 (mPriority);
 onLooperPrepared();
 ();
 mTid = -1;
}

public Looper getLooper() {
 if (!isAlive()) {
  return null;
 }

 // If the thread has been started, wait until the looper has been created.
 synchronized (this) {
  while (isAlive() && mLooper == null) {
   try {
    wait();
   } catch (InterruptedException e) {
   }
  }
 }
 return mLooper;
}

public boolean quit() {
 Looper looper = getLooper();
 if (looper != null) {
  ();
  return true;
 }
 return false;
}
}

Summarize

  • Key categories: HandlerThread, Handler, Looper, MessageQueue, Messaga
  • MessageQueue data structure, linked list.

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