Looper, Handler in Android Part 2 - Handler

Efficient Progress Tracking with Handlers and Threads in Android Development

Looper, Handler in Android Part 2 - Handler

This article is a continuation of our exploration into the Android's Looper and Handler. If you haven't read part 1 yet, I recommend revisiting it, as we won't be revisiting those concepts here. In this post, we will look at Handler class and its relationship with Looper and Message classes.

Construction of Handler

Just as Looper in Android is linked to a single thread, it's important to note that a Handler is similarly tied to a single Looper. However, unlike the Looper you can create multiple objects of the Handler class within the same thread.

Application developers have two constructors at their disposal for creating a new Handler class:

public Handler (Looper looper)
public Handler (Looper looper, Handler.Callback callback)

Note: Two additional public constructors, public Handler() and public Handler(Handler.Callback callback), are now deprecated. If you encounter these constructors or choose to use them for some reason, be aware that they internally use the looper obtained via the Looper.myLooper() method.

Here is the actual constructor of the handler, not accessible to app developers. I'm presenting it here to shed light on the inner workings of the handler:

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async, boolean shared) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    mIsShared = shared;
}

Note: This multipart article won't cover the details of mAsynchronous and mIsShared.

Message and Callback classes

Before comprehending the role of the Handler, let us examine the definitions of the Handler.Callback and Message classes

1. Handler.Callback

The Callback interface is defined inside Handler class as follows:

public interface Callback {
    /**
    * @param msg A {@link android.os.Message Message} object
    * @return True if no further handling is desired
    */
    boolean handleMessage(@NonNull Message msg);
}

2. Message

The Message class has various fields and methods, but for our use case we'll focus on a subset of those:

public final class Message implements Parcelable {
    /**
     * User-defined message code so that the recipient can identify
     * what this message is about. Each {@link Handler} has its own name-space
     * for message codes, so you do not need to worry about yours conflicting
     * with other handlers.
     */
    public int what;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg1;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg2;

    /**
     * An arbitrary object to send to the recipient.  When using
     * {@link Messenger} to send the message across processes this can only
     * be non-null if it contains a Parcelable of a framework class (not one
     * implemented by the application).   For other data transfer use
     * {@link #setData}.
     *
     * <p>Note that Parcelable objects here are not supported prior to
     * the {@link android.os.Build.VERSION_CODES#FROYO} release.
     */
    public Object obj;

    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUsage
    /*package*/ Runnable callback;
}

Relationship between Handler, Message and Looper

Handler is used to interact with the Message loop i.e. Looper.loop().

In part 1, we delved into a high-level understanding of the Looper.loop() method. Now, let's take a close look at the implementation of this method.

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    /* << unrelated code removed for comprehension >> */
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    /* << unrelated code removed for comprehension >> */
    try {
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        /* << unrelated code removed for comprehension >> */
        throw exception;
    }
    /* << unrelated code removed for comprehension >> */
}

Note: I've included only the essential code for our comprehension, but you can refer to the complete code here.

Here, msg.target has reference to the instance of the Handler class and the Looper calls the dispatchMessage() method of the Handler class in each loop. This occurs inside the Thread where the Looper is looping. Same can be visualised in below sequence diagram uml sequence diagram of looper.loop method

By now, we've covered Looper, Handler, and the Message class and have an understanding of the role of the MessageQueue.

In the upcoming article, we will consolidate our knowledge gained so far and apply it in real-world scenarios to effectively utilize the Handler class. Additionally, we will closely examine the dispatchMessage() method to deepen our understanding of its role in the overall process.