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()
andpublic 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 theLooper.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
andmIsShared
.
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
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.
Subscribe to my newsletter
Read articles from Sajal Raj Gautam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sajal Raj Gautam
Sajal Raj Gautam
I’m a software engineer with a focus on Android applications and framework development. I specialize in tackling complex challenges with innovative solutions. I’ve worked across various form factors and software layers, contributing to the various software development lifecycle—design, development, testing, planning, and performance optimization. Leading teams, I ensure quality deliverables within deadlines. Beyond Android, I’ve gained hands-on experience in Robotics, IoT devices, RTOS applications, and Machine Learning. My dynamic career journey reflects my enthusiasm for learning and adapting to new technologies. I’m eager to explore opportunities and contribute to cutting-edge developments in the tech industry.