Looper, Handler in Android Part 4 - Handler

Understanding the flow of message using sendMessage() and handleMessage() method

This is the final article in our series on Introduction Looper and Handler in Android. To fully grasp the concepts discussed here, it's assumed that you have read or are familiar with the content covered in previous parts. If not, I recommend revisiting those articles first.

Recap of How to Use Handler

Handler can be utilized in two main ways:

  1. Passing Runnable: Adding or posting operation to be executed directly inside the thread from another thread. This is accomplished using the post() method of Handler.

  2. Sending Message: Sending a message about an operation that needs to be done inside the thread from another thread. This involves a combination of the sendMessage() and handleMessage() methods of Handler.

    While we coverted the post() method extensively in part3, here we will delve into the sendMessage() and handleMessage() methods of the Handler.

When to use sendMessage()

As the name suggests, sendMessage() is used to send a Message object to a Handler and handleMessage() will perform certain operation on the received Message. The two methods, sendMessage() and handleMessage(), must always be used in conjunction. If you usesendMessage() to send a Message to a Handler, the Handler must also implement its handleMessage() to process that message.

Unlike post(), which is suitable for executing a block of code, sendMessage() is used for reusable pieces of code that need to be handled frequently within your thread. For example, when multiple parts of your code needs to save documents, you can use sendMessage() to organize your code more efficiently. All the logic related to file operations will be handled separately, enabling reuse, concurrency, separation of concerns, and synchronous access to the file I/O operations. By passing the path as an argument, the same code can be utilized for multiple other documents, further enhancing its flexibility and maintainability.

Note: In terms of synchronicity, The sendMessage() call itself is asynchronous, non-blocking and can be executed from any thread. The handleMessage() method associated with it will always be invoked synchronously and will run on the same thread that the Handler operates on.

Internals of sendMessage()

Internally both post() and sendMessage() work similarly. The difference lies in how they are handled as seen above. Typically, post() is used for running a one-off block of code in another thread (e.g., updating the UI), and sendMessage() is used for operations that need to be repeated. However, there is no strict rule in the language construct, and it largely depends on your use case and programming style.

To reiterate, here's an excerpt from the Android documentation of Handler

The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

Similar to post(), sendMessage() also calls sendMessageDelayed() function to add the Message to the MessageQueue (refer to part 3 for the detailed flow of post()). The only difference lies in the dispatchMessage method which calls handleMessage() instead.

    // SajalRG: adds message to the queue, called from any thread
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    /**
     * Handle system messages here.
     */
   // SajalRG: executed from Looper.loop() inside the thread represented by handler
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Example from AOSP SystemUI

Let's examine an example from AOSP on how this pair can be utilized. We will take a look at the CommandQueue class.

Note: Our objective here is not to delve into the intricacies of the CommandQueue class itself. Instead, we aim to focus solely on how it leverages the sendMessage() and handleMessage() methods. This approach provides insight into real-world usage in larger projects, offering a practical understanding rather than relying on contrived examples.

/**
 * This class takes the functions from IStatusBar that come in on
 * binder pool threads and posts messages to get them onto the main
 * thread, and ...
 */
public class CommandQueue extends IStatusBar.Stub implements
        CallbackController<Callbacks> {

In this example, CommandQueue also extends the IStatusBar.stub(), which we can ignore for now but it could be a topic for furture articles on Binder and AIDL. CallbackController is an interface which the CommandQueue implements, but we won't explore it further in this post.

For our purpose, CommandQueue has some methods that are called from other threads, referred to as binder pool threads, and it will post the messages onto the main thread.

Understanding CommandQueue

If you view the CommandQueue class from this link, you might notice that there is no sendMessage() call in the class, even though we are using this as an example for sendMessage().

To understand this, let's look at Message class. We partially covered the Message class in part 2, please review if you haven't already. Now, let's introduce method sendToTarget class which is used here. It internally calls the sendMessage() for us. Both approaches are okay.

    /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }

Also, instead of using Message.obtain() to create new Message, as we saw in part3, Handler.obtainMessage() is used here, which internally does the same. Below is the excerpt from the Handler class listing all other possible ways to obtain Message.

    @NonNull
    public final Message obtainMessage() {
        return Message.obtain(this);
    }

    /**
     * Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message.
     * 
     * @param what Value to assign to the returned Message.what field.
     * @return A Message from the global message pool.
     */
    @NonNull
    public final Message obtainMessage(int what) {
        return Message.obtain(this, what);
    }

    /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
     * of the returned Message.
     * 
     * @param what Value to assign to the returned Message.what field.
     * @param obj Value to assign to the returned Message.obj field.
     * @return A Message from the global message pool.
     */
    @NonNull
    public final Message obtainMessage(int what, @Nullable Object obj) {
        return Message.obtain(this, what, obj);
    }

    /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned
     * Message.
     * @param what Value to assign to the returned Message.what field.
     * @param arg1 Value to assign to the returned Message.arg1 field.
     * @param arg2 Value to assign to the returned Message.arg2 field.
     * @return A Message from the global message pool.
     */
    @NonNull
    public final Message obtainMessage(int what, int arg1, int arg2) {
        return Message.obtain(this, what, arg1, arg2);
    }

    /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the 
     * returned Message.
     * @param what Value to assign to the returned Message.what field.
     * @param arg1 Value to assign to the returned Message.arg1 field.
     * @param arg2 Value to assign to the returned Message.arg2 field.
     * @param obj Value to assign to the returned Message.obj field.
     * @return A Message from the global message pool.
     */
    @NonNull
    public final Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj) {
        return Message.obtain(this, what, arg1, arg2, obj);
    }

In above code snippet, different signature which allows us to pass additional information to the Message are also listed. Here, what is primarily used in handleMessage() identify which operation to perform.

Message flow in CommandQueue

Let's explore the addQsTile method to explore the message flow. First, the method is called from the binder thread.

    // SajalRG: create new handler using the main looper
    private Handler mHandler = new H(Looper.getMainLooper());
    @Override
    public void addQsTile(ComponentName tile) {
        synchronized (mLock) {
            // SajalRG: get message object and send the message
            mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
        }
    }

Then, it is handled inside the Handler

    private final class H extends Handler {
        private H(Looper l) {
            super(l);
        }
        // SajalRG: handleMessage is called in conjunction to the sendMessage
        public void handleMessage(Message msg) {
            final int what = msg.what & MSG_MASK;
            // SajalRG: what is used to identify which operation to execute
            switch (what) {
                // SajalRG: other cases
                case MSG_ADD_QS_TILE:
                    // SajalRG: actual operation happening inside the main thread
                    for (int i = 0; i < mCallbacks.size(); i++) {
                        mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
                    }
                    break;
                // SajalRG: other cases
            }
        }
    }

What's next

This post concludes our introduction to Handler, Looper and Thread. I hope you have a clearer understanding of the subject and how they are interconnected at a high level. I'd still like to continue this series addressing some misconceptions I'd come across and answer some frequently asked questions related to this topic. If you have any specific topics or query you'd like me to cover, please feel free to leave a comment.

Your support serves as great encouragement for me to continue creating and sharing content.