This article has been translated into Chinese
Discover the Android Killer — HandlerWelcome to
“Gold Digging Translation Project”Translation of quality technical articles.
For an Android application to be responsive, you need to prevent the UI thread from blocking. Responsiveness also increases when blocking or computationally intensive tasks are offloaded to worker threads. The results of these operations often need to update UI components, which must be performed on the UI thread. Mechanisms like blocking queues, shared memory, and pipes impose blocking problems for the UI thread. To prevent this issue, Android provides its own message passing mechanism — the Handler[1]. The Handler is a fundamental component in the Android framework. It offers a non-blocking message passing mechanism. Neither the producer nor the consumer block during message hand-offs.
Though Handlers are used frequently, It’s easy to overlook how they work. This article takes a deeper look into the various components of the Handler explains why the Handler is a powerful component that goes beyond worker threads and UI thread communication.
Image Viewer Example
Let’s start with an example of how we might use a Handler in an application. Consider an activity that needs to display An image touchdown from the network. There are several approaches to do this. In this example, We’ll start a new worker thread to perform the network call and retrieve the image payload.
An alternative is to use Handler Messages instead of Runnables.
In this second example, a worker thread fetches an image from the network. Once the image downloads, We need to update the ImageView with the bitmap. We know we can’t touch the UI components from a non-UI thread, so we use a Handler. The Handler acts as a mediator between the worker thread and the UI thread. The message is enqueued by the Handler on the worker thread and processed by the Handler on the UI thread.
A Deeper Look Inside the Handler
The components of a Handler are:
- Handler
- Message
- Message Queue
- Looper
We’ll look at each component and see how they interact with one another.
Handler
The Handler[2] is the immediate interface for message passing between threads. Both the consumer and producer threads interact with the Handler by invoking the following operations:
- creating, inserting, or removing Messages from the Message Queue
- processing Messages on the consumer thread
Each Handler is associated with a Looper and a Message Queue. There are two ways to create a Handler:
- through the default constructor, which uses the Looper associated with the current thread
- by explicitly specifying which Looper to use
A Handler can’t function without a Looper because it can’t put messages in the Message Queue. Thus, it won’t receive any messages to process.
public Handler(Callback callback, boolean async) { // code removed for simplicity mLooper = Looper.myLooper(); If (mLooper == null) {throw new RuntimeException(" Can't create handler inside thread that has not been called Stars. Prepare () "); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code
The snippet from Android Source (above) demonstrates the logic of constructing a new Handler. The handler checks if the current thread has a valid Looper. If not, Throws a Runtime Exception. The Handler then receives a reference to The Looper’s Message Queue.
Note: Multiple Handlers associated with the same thread share the same Message Queue because they share the same Looper.
The Callback is an optional argument. If provided, it processes messages dispatched by the Looper.
Message
The Message[3] acts as a container for arbitrary data. The producer thread sends Messages to the Handler, which enqueues to the Message Queue. The Message provides three pieces of extra information, required by the Handler and Message Queue to process the message:
- what– An identifier the Handler can be used to distinguish messages and process them differently
- time– Informs the Message Queue when to process a Message
- target— Indicates which Handler should process the Message
Message creation typically uses of one of the following Handler methods:
public final Message obtainMessage() public final Message obtainMessage(int what) public final Message obtainMessage(int what, Object obj) public final Message obtainMessage(int what, int arg1, int arg2) public final Message obtainMessage(int what, int arg1, int arg2, Object obj)Copy the code
The Message is obtained from the message pool. The supplied arguments populate the fields of the Message. The Handler also sets the Message’s target to itself. This allows us to chain the call as such:
mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();Copy the code
The Message pool is a LinkedList of Message objects with a maximum pool size of 50. After the Handler processes the Message, the Message Queue returns the object to the pool and resets all fields.
When posting a Runnable to the Handler via post(Runnable r), the Handler implicitly constructs a new Message. It also sets the callback field to hold the Runnable.
Message m = Message.obtain();
m.callback = r;Copy the code
At this point we can see the interaction between a producer thread and a Handler. The producer creates a Message and sends it to the Handler. The Handler then enqueues the Message into the Message Queue. The Handler processes the Message on the consumer thread sometime in the future.
Message Queue
The Message Queue[4] is an unbounded LinkedList of Message objects. It inserts Messages in time order, where the lowest timestamp dispatches first.
The MessageQueue also maintains a dispatch barrier that represents the current time according to SystemClock.uptimeMillis. When a Message timestamp is less than this value, the message is dispatched and processed by the Handler.
The Handler offers three variations for sending a Message:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)Copy the code
Sending a message with a delay sets the message’s time field as SystemClock. UptimeMillis () + delayMillis.
Messages sent with a delay have the time field set to SystemClock.uptimeMillis() + delayMillis. Whereas, Messages sent to the front of the queue have the time field set to 0, and process on the next Message loop iteration. Use this method with care as it can starve the message queue, cause ordering problems, or have other unexpected side-effects.
Handlers are often associated with UI components, which reference an Activity. The reference from the Handler back to these components can potentially leak the Activity. Consider the following scenario:
In this example, the Activity starts a new worker thread to download and display an image in an ImageView. The worker thread communicates UI updates via the UIHandler, which retains references to Views and updates their state (toggle visibility, set the bitmap).
Let’s assume the worker thread is taking long to download the image due to slow network the worker thread completes results in an Activity leak. There are two strong references in this example. One between the worker thread and UIHandler, and another between the UIHandler and the views. This prevents the Garbage Collector from reclaiming the Activity reference.
Now, let’s look at another example:
In this example, the following sequence of events occur:
- A PingHandler is created
- The Activity sends a delayed Message to the Handler, which enqueues into the MessageQueue
- The Activity is destroyed before the Message dispatches
- The Message is dispatched and processed by the UIHandler, and a log statement is output
Though it may not be apparent at first, the Activity leaks in this example as well.
After destroying the Activity, the Handler reference should be available for Garbage Collection. However, when we create a Message object, it retains a reference to the Handler:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}Copy the code
The Android Source snippet (above) shows that all Messages sent to a Handler eventually invoke enqueueMessage. Notice that the Handler reference is explicitly assigned to msg.target. This tells the Looper which Handler should process the message when it’s retrieved from the MessageQueue.
The Message is added to the MessageQueue, which now holds a reference to the Message. The MessageQueue also has a Looper associated with it. An explicit Looper Mattress until it ‘s terminated, Whereas the Main Looper lives as long as the application does. The Handler reference lives as long as the Message isn’t Recycled by the MessageQueue. Once it’s recycled, it’s fields, including the target reference, are cleared.
Though there is a long living reference to the Handler, it isn’t clear if the Activity leaks. To check for a leak, we must determine if the Handler also references the Activity within the class. In this example, It does. There’s an implicit reference across by a non-static class member to its enclosing class. Specifically, The PingHandler wasn’t declared as a static class, so it has an implicit reference to the Activity.
Using a combination of a WeakReference and a static class modifier prevents the Handler from leaking the Activity. When the Activity is destroyed, the WeakReference allows the Garbage Collector to reclaim the object you want to retain (typically an Activity). The static modifier on the inner Handler class prevents an implicit reference to the parent class.
Let’s modify our UIHandler from this example to address this concern:
Now, the UIHandler constructor takes in the Activity, which is wrapped in a WeakReference. This allows the Garbage Collector to reclaim the activity reference when the activity is destroyed. To interact with UI components of the Activity, we need a strong reference to the Activity from mActivityRef. Since we’re using a WeakReference, we must exercise caution when accessing the Activity. If the only path to an Activity reference is through a WeakReference, the Garbage Collector may have already reclaimed it. We need to check if that’s happened. If it has, the Handler is effectively irrelevant and the messages should be ignored.
Though this logic addresses leaking memory, there’s still a problem with it. Yet the Garbage Collector Hasn’t Reclaimed the Reference. Depending on the operation being performed, this can potentially crash your application. To work around this, we need to detect what state the activity is in.
Let’s update the UIHandler logic to account for these scenarios
Now, we can generalize the interaction between a MessageQueue, the Handler, and Producer Threads:
In the figure (above), multiple producer threads submit Messages to different Handlers. However, each Handler is associated with the same Looper, so all Messages publish to the same MessageQueue. This is important because Android creates several Handlers associated with the Main Looper:
- The Choreographer: handles vsync and frame updates
- The ViewRoot: handles input and window events, configuration changes, etc.
- The InputMethodManager: handles keyboard touch events
- And several others
Tip: Ensure that producer threads aren’t spawning several Messages, as they may starve processing system generated Messages.
Debugging Tips: You can debug/dump all Messages dispatched by a Looper by attaching a LogPrinter:
final Looper looper = getMainLooper();
looper.setMessageLogging(new LogPrinter(Log.DEBUG, "Looper"));Copy the code
Similarly, you can debug/dump all pending Messages in a MessageQueue associated with your Handler by attaching a LogPrinter to your Handler:
handler.dump(new LogPrinter(Log.DEBUG, "Handler"), "");Copy the code
Looper
The Looper[5] reads messages from the Message Queue and dispatches execution to the target Handler. Once a Message passes the dispatch barrier, it’s eligible for the Looper to read it in the next message loop. The Looper blocks when no messages are eligible for dispatch. It resumes when a Message is available.
Only one Looper can be associated with a Thread. Attaching another Looper to a Thread results in a RuntimeException. The use of a static ThreadLocal object in the Looper class ensures that only one Looper is attached to the Thread.
Calling Looper.quit terminates the Looper immediately. It also discards any Messages in the Message Queue that passed the dispatch barrier. Calling Looper.quitSafely ensures all Messages ready for dispatch are processed before pending messages are discarded.
The Looper is setup in the run() method of a Thread. A call to the static method Looper.prepare() checks if a preexisting Looper is associated with this Thread. It does this by using the Looper’s ThreadLocal to check if a Looper object already exists. If it doesn’t, a new Looper object and a new MessageQueue are created. The Android Source snippet (below) demonstrates this.
Note: The public prepare Looper method invokes prepare(true) internally.
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)); }Copy the code
The Handler can now receive Messages and add them to the Message Queue. Executing the static Looper.loop () method will start reading the Messages off the queue. Each loop iteration retrieves the next message, dispatches it to the target Handler, and recycles it back to the Message pool. Looper.loop will continue this process until the Looper is terminated. The Android Source snippet (below) demonstrates this:
public static void loop() { if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); msg.recycleUnchecked(); }}Copy the code
It’s not necessary to create your own thread that has a Looper attached to It. Android provides a convenience class for This — HandlerThread. This extends the Thread class and manages the creation of a Looper. The snippet below describes a typical usage pattern:
private final Handler handler;
private final HandlerThread handlerThread;Copy the code
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate();
handlerThread = new HandlerThread("HandlerDemo");
handlerThread.start();
handler = new CustomHandler(handlerThread.getLooper());
}Copy the code
@Override
protected void onDestroy() {
super.onDestroy();
handlerThread.quit();
}Copy the code
The onCreate() method constructs a new HandlerThread. When the HandlerThread starts, it prepared the Looper and attaches it to the thread. The Looper now begins processing messages off the MessageQueue on the HandlerThread.
Note: When the activity is destroyed, it’s important to terminate the HandlerThread. This also terminates the Looper.
Summary
The Android Handler plays an integral role in an Application’s lifecycle. It sets the foundation of the Half-Sync/Half-Async architectural pattern. Various internal and external sources rely on the Handler for asynchronous event dispatching, as it minimizes overhead and maintains thread safety.
A deeper understanding of how A component works helps resolve hard problems. It also allows us to use the component’s APIs optimally. We often use the Handler as a mechanism for worker to UI thread communication, But it’s more than that. The Handler appears in The IntentService[6], The Camera2[7] APIs, And many others. In these APIs, it’s used more generally to focus on redirect between arbitrary threads.
We can apply this deeper understanding of the Handler to building more efficient, simple, and robust applications.