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 {
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 {
public int what;
public int arg1;
public int arg2;
public Object obj;
@UnsupportedAppUsage
Handler target;
@UnsupportedAppUsage
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.");
}
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();
if (msg == null) {
return false;
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
throw exception;
}
}
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

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.