Mobile Development 29 min read

In‑Depth Analysis of Android Handler Mechanism, Looper, MessageQueue and Their Practical Applications

The article dissects Android’s Handler‑Looper‑MessageQueue architecture, explaining ThreadLocal‑based Looper binding, various Handler constructors, async versus sync messages, barrier handling, IdleHandler, HandlerThread and IntentService usage, and the differences between Handler.post and View.post for responsive UI development.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
In‑Depth Analysis of Android Handler Mechanism, Looper, MessageQueue and Their Practical Applications

This article provides a comprehensive source‑code‑driven explanation of the Android Handler mechanism, covering its core components, creation patterns, and common pitfalls.

1. Basic Principle Review

Handler, Thread (ThreadLocal), Looper, MessageQueue and Message form the five pillars of Android's message‑driven architecture. The UI thread’s event loop is built on this mechanism, making it essential knowledge for every Android developer.

2. Thread (ThreadLocal)

Each thread can have at most one Looper, which is stored in a ThreadLocal object to achieve thread isolation.

static final ThreadLocal
sThreadLocal = new ThreadLocal
();
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));
}

3. Handler Constructors

Handler can be instantiated in several ways, e.g.:

Handler();
Handler(Callback callback);
Handler(Looper looper);
Handler(Looper looper, Callback callback);
Handler(boolean async);
Handler(Callback callback, boolean async);
Handler(Looper looper, Callback callback, boolean async);

Two main creation modes are highlighted:

Without passing a Looper – you must call Looper.prepare() beforehand.

With a specific Looper – the Handler binds to that Looper, allowing cross‑thread communication.

4. Async Parameter

When async is true, all messages posted through that Handler are marked asynchronous, bypassing the normal synchronization barrier.

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

5. Callback Parameter

The callback is invoked when a Message’s callback field is non‑null, otherwise the Handler’s own Callback or handleMessage() is used.

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

6. Looper

Looper creates a message loop for a thread. The main thread’s Looper is automatically prepared in ActivityThread.main() via Looper.prepareMainLooper() .

public static void main(String[] args) {
    Looper.prepareMainLooper();
    Looper.loop();
}

7. MessageQueue and Message

MessageQueue stores messages ordered by their when timestamp. Synchronous, asynchronous and barrier messages are distinguished.

Barrier messages have target == null and use arg1 as a token. They block synchronous messages while allowing asynchronous ones to pass.

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        // Insert into the queue …
        return token;
    }
}

public void removeSyncBarrier(int token) {
    synchronized (this) {
        // Locate and remove the barrier message …
    }
}

8. Barrier Message Usage

Android’s UI rendering uses a barrier to prioritize the traversal Runnable (asynchronous) over later synchronous messages, ensuring smooth frame rendering.

if (!mTraversalScheduled) {
    mTraversalScheduled = true;
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}

9. IdleHandler

IdleHandler runs when the MessageQueue is idle. It is widely used for background tasks such as forced GC (GcIdler) or library initialisation.

public interface IdleHandler {
    /** Called when the queue is idle. Return true to keep active. */
    boolean queueIdle();
}

final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

10. HandlerThread

HandlerThread is a regular Thread that prepares its own Looper, allowing tasks to be posted without creating multiple threads.

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

It can be stopped via quit() , which forwards the request to the underlying Looper.

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

11. IntentService (HandlerThread + Service)

IntentService creates a HandlerThread internally, processes each start request as a Message, and stops itself after handling the last Intent.

public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

private final class ServiceHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent) msg.obj);
        stopSelf(msg.arg1);
    }
}

12. Handler.post vs View.post

Handler.post posts a Runnable to the main thread’s MessageQueue; its execution depends on when the current message (e.g., Activity resume) finishes. View.post first checks if the View is attached (non‑null mAttachInfo ). If attached, it posts directly; otherwise it stores the Runnable in a temporary RunQueue that is flushed during dispatchAttachedToWindow() after the View is attached and measured.

This explains why View.post can obtain view dimensions even in onCreate , while a plain Handler.post cannot.

Conclusion

The Handler‑Looper‑MessageQueue architecture is the backbone of Android’s UI thread. Understanding ThreadLocal‑based Looper binding, async vs sync messages, barrier messages, IdleHandler, and the nuances of Handler.post versus View.post enables developers to write efficient, responsive applications and avoid common pitfalls such as UI jank or memory leaks.

Mobile DevelopmentAndroidConcurrencyLooperHandlerMessageQueue
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.