The message mechanism in Android mainly refers to the operation mechanism of Handler, and the operation of Handler is inseparable from Looper and MessageQueue.

MessageQueue

  • MessageQueue is used to receive the Message sent by Hnadler, and its internal data structure is a single linked list to store the Message list.
//MessageQueue.class
Message next() {...for(;;) {... synchronized (this) { ...if(msg ! = null) {if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    returnmsg; }}else{ // No more messages. nextPollTimeoutMillis = -1; }... }... }}Copy the code
  • MessageQueue is created when the prepare method of Looper is called.
//Looper.class
public static void prepare() {
    prepare(true);
}

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)); } private Looper(Boolean quitAllowed) {// Create MessageQueue mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code
  • MessageQueue’s next method is an infinite loop that will not stop until the quit method is called
Message next() {...for(;;) {... synchronized (this) { // Process the quit message now that all pending messages have been handled.if(mQuitting) { dispose(); // Return null to exit the loopreturnnull; }}... }... } void quit(boolean safe) { ... synchronized (this) { ... // So MessageQueue exits the looptrue; . }}Copy the code

Looper

  • Looper is used to retrieve pending messages from MessageQueue, calling MessageQueue’s next method to retrieve the message. If there is no message in the MessageQueue, Looper blocks and waits until the MessageQueue returns NULL to exit the loop listening for the message.
  • MessageQueue and Looper are created after the prepare method for Looper is called. But you need to call Looper’s loop method to start the message loop, and Looper will start the loop listening for messages in the MessageQueue
  • Looper retrieves the Message and calls the dispatchMessage method of the Handler corresponding to the Message to process the Message. Handler’s dispatchMessage method will then be executed in the same thread as Looper.
public static void loop() { final Looper me = myLooper(); Looper and MessageQueue must be created using the prepare method before calling the loop methodif (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for(;;) {// Call MessageQueue's next method to fetch the Message, because next is an infinite loop, so loop method will block Message MSG = queue.next(); // might block // MessageQueue will return null only after the quit method is calledif (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }... Try {// MSG. Target is the Handler, which calls Handler's dispatchMessage method to process the message. msg.target.dispatchMessage(msg); } finally {if(traceTag ! = 0) { Trace.traceEnd(traceTag); }}... }}Copy the code

Handler

  • After Looper gets the message, it is finally sent to Handler’s dispatchMessage method for processing
//Handler.class
public void dispatchMessage(Message msg) {
    if(msg.callback ! = null) { handleCallback(msg); }else {
        if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code
  • First check to see if Message’s CallBAC is null. If not, the Message is processed through the handleCallback method.
private static void handleCallback(Message message) {
    message.callback.run();
}
Copy the code
  • The callback to Message is a Runnable object, which is a Runnable parameter passed by the Handler’s POST method.
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
Copy the code
  • If the callback argument is null, the mCallback argument is checked for null. The handleMessage method of mCallback is called without null to process the message. MCallback is a Callback interface
/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}
Copy the code
  • If we don’t want to subclass Handler when we use Handler, we can use Callback
private Handler callbackHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {

        return false; }});Copy the code
  • If the mCallback argument is also null, the handleMessage method is called to process the message, which is the handleMessage method we overrode when we created the Handler as a Handler subclass
//Handler.class
/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}
Copy the code
  • Create Handler by subclassing Handler
private static class MyHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case MESSAGE_TAG:
                Log.d(Thread.currentThread().getName(),"receive message");
                break;
            default:

                break; }}}Copy the code

Why can Handler switch threads?

A summary: Handler is an object shared between threads, while Looper is private to each thread.

What to make of the above statement?
  • Handler is shared meaning that the Handler object we created can be accessed by both threads that need to be switched, such as calling Handler’s sendMessage method.
  • For example, in the following code, the myHandler variable is created in the onCreate method of the HandlerActivity, in the UI thread. MyHandler can be called either in the UI thread or in a child thread.
public class HandlerActivity extends Activity { public static final int MESSAGE_TAG = 0x01; private MyHandler myHandler; Private static class MyHandler extends Handler {@override public void MyHandler extends Handler {@override public void MyHandler extends Handler {@override public void MyHandler extends Handler handleMessage(Message message) { switch (message.what) {case MESSAGE_TAG:
                    Log.d(Thread.currentThread().getName(),"receive message");
                    break;
                default:

                    break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyHandler = new myHandler (); new Thread(newRunnable() {
            @Override
            public void run() { Message message = new Message(); message.what = MESSAGE_TAG; // Call myHandler created by the UI thread in the child thread and send the message myHandler.sendMessage(message); } }).start(); }}Copy the code
  • How can I switch from a child thread to a UI thread after I call Handler to send a message? This is because every Handler created requires the current thread to have a Looper, and each thread will have its own Looper
//Handler.class
public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    ...
    //获取Looper,如果当前线程没有Looper就会抛异常
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } // looper. class // Each thread has its own ThreadLocal, Static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); Private static void prepare(Boolean quitAllowed) {// Each thread can have only one Looperif(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); Sthreadlocal. set(new Looper(quitAllowed)); } public static @nullable Looper from ThreadLocalmyLooper() {
    return sThreadLocal.get();
}
Copy the code
  • As you can see from the above source code, there is a Looper for each Handler, and each thread has its own Looper, and only one Looper per thread. Although we can create more than one Handler per thread, it actually corresponds to a Looper.
//Handler.class
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true); } // Insert a message into MessageQueuereturn queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
  • As you can see from the source code above, all Handler’s sendMessage method does is insert the Message into the corresponding MessageQueue. Looper calls MessageQueue’s next method to fetch the added information, and Looper calls Handler’s handleMessage method to process the information.
  • And because of our analysis above, Looper is unique to each thread. The thread in which the Looper is located is the thread in which the logic to call Handler’s handleMessage method is executed.
  • So Handler’s handleMessage method runs in the thread that created the Handler.

conclusion

Handler mechanism

Let’s summarize the Handler mechanism with the example above of subclassing Handler

  • First we create the Handler in the thread that needs to execute the Handler’s handleMessage method, such as in the UI thread, so that we can send a message to the Handler later after executing the time-consuming task in the child thread. Finally, update the UI in the Handler’s handleMessage method
  • After executing a time-consuming task such as a network request in a child thread, encapsulate the result in a Message and insert the Message into the Handler’s Message queue using the Handler’s sendMessage method
  • Handler’s Looper retrieves the message from MessageQueue and sends it to Handler to call the handleMessage method. This switches the logic from the child thread to the UI thread and brings back the result of the child thread’s execution

The Handler we created in the UI thread is the Looper of the UI thread. When we insert a message in a child thread by calling sendMessage, Handler’s Looper retrieves the message and calls Handler’s handleMessage method. So the thread on which the handleMessage method is executed is the thread on which the Looper is executed, thus switching from the child thread calling the sendMessage method to the thread calling the handleMessage Looper.

There are three ways to use Handler
  • There are three ways to use Handler: subclass Handler, Callback interface, and post a Runnable
  • Post a Runnable Callback to the Callback interface. This is not a common way to post a Runnable Callback
public class HandlerActivity extends Activity {
    private Handler myHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myHandler = new Handler();

        new Thread(new Runnable() {
            @Override
            public void run() {
                myHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(Thread.currentThread().getName(),"receive message"); }}); } }).start(); }}Copy the code
  • It is important to understand that the thread that executes the run method above is also the thread that creates the Handler, in this case the UI thread. This is essentially putting the code from the handleMessage method directly into the Run method.
Pay attention to the point
  • The main thread, the Looper and message queue in the UI thread, are already created for us when we start the application, so we can create the Handler directly in the Activity without reporting an error
  • The underlying MessageQueue MessageQueue is a single linked list structure, and insertion and deletion have advantages. Multiple messages sent by the Handler are queued in the order in which they are sent.
  • Since Looper’s loop method is an infinite loop, the Handler created in the child thread needs to call Looper’s quit method to exit the loop when it is clear that it is no longer needed.

Did you improve today? Welcome to follow my wechat public number, and make progress with me every day!