Public number: byte array, hope to help you ๐Ÿ˜‡๐Ÿ˜‡

Handler plays a very important role in the entire Android development system. It is a standard event-driven model for developers to play a very clear role, which is to achieve thread switching or to perform delayed tasks. A slightly more advanced use may be to ensure that multiple tasks are executed in order. Due to the special status of the main thread in Android, tripartite libraries such as EventBus and Retrofit, which are not unique to Android, use handlers to support Android’s particular platform. Most developers are already familiar with how to use Handler. Here is a look at the internal implementation of the Handler, which I hope will help you ๐Ÿ˜‡๐Ÿ˜‡

This article is based on Android API 30 (Android 11) system source code to explain

First, to implement Handler

This article will not introduce the source code directly at the beginning, but will first be based on the effect we want to achieve the source code backwards, step by step to implement a simple handler.html

1, the Message

First, we need a vector to represent the task to be executed. Let’s call it Message. What parameters should Message take?

  • We need to have a unique identifier, because there may be multiple tasks to perform, and we need to know which is which, and an Int is enough
  • You need to be able to hold data. There are many different types of data you need to send, so you can use an Object variable to represent the data
  • You need a variable of type LONG to represent the execution timestamp of the task

Therefore, the Message class should contain at least the following fields:

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
public final class Message {
    // Unique identifier
    public int what;
    / / data
    public Object obj;
    / / timestamp
    public long when;
}
Copy the code

2, MessageQueue

Since messages are not sent and consumed immediately, there must be a place to store them. Call it a MessageQueue, or Message queue. Message may need to be delayed, so MessageQueue should store messages in sequence according to the size of time stamps when saving messages. Messages with small time stamps should be placed at the head of the queue, and values can be directly taken from the queue head when consuming messages

So what data structure is good for storing messages?

  • Use an array? Not suitable. Arrays are faster to traverse, but require a fixed amount of memory in advance, which can result in large amounts of data being moved when inserting and removing data. MessageQueue may receive a variable number of messages and a variable timestamp size at any time, and need to remove the Message from the queue after consuming it, so using arrays is not appropriate
  • Use the list? It seems possible. The linked list only needs to change the reference of pointer when inserting and removing data. There is no need to move data, and the memory space only needs to be allocated as needed. Although linked lists perform poorly on random access, it doesn’t matter for MessageQueue because messages are consumed only by fetching the value of the queue header, not by random access

Ok, now that you’ve decided to use the list structure, Message needs to add a field to point to the next Message

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
public final class Message {
    // Unique identifier
    public int what;
    / / data
    public Object obj;
    / / timestamp
    public long when;
	// Next node
    public Message next;
}
Copy the code

MessageQueue needs to provide an enqueueMessage method to insert messages to the linked list. Since it is possible for multiple threads to send messages to the queue at the same time, thread synchronization is required within the method

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
public class MessageQueue {

    // The first message in the list
    private Message mMessages;
    
    void enqueueMessage(Message msg, long when) {
        synchronized (this) {
            Message p = mMessages;
            MSG is used as the head of the list if the list is empty, or if the timestamp of the message at the head of the queue is larger than MSG
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                // Find the first message in the list whose timestamp is larger than MSG and insert MSG in front of the message
                for(; ;) { prev = p; p = p.next;if (p == null || when < p.when) {
                        break; } } msg.next = p; prev.next = msg; }}}}Copy the code

In addition, MessageQueue needs to have a method to get the header message, called next(). It is possible to send messages to MessageQueue at any time, so the next() method starts an infinite loop inside to iterate. If the current header message can be processed directly (that is, the timestamp of the message is less than or equal to the current time), the header message is returned directly. If the timestamp of the queue header message is larger than the current time (that is, the queue header message is a delayed message), calculate the difference between the current time and the timestamp of the queue header message, calculate how long the next() method needs to wait for blocking, and call the nativePollOnce() method to wait for some time before continuing the loop

    // is used to indicate whether the next() method is in a blocking wait state
    private boolean mBlocked = false;

    Message next() {
        int nextPollTimeoutMillis = 0;
        for(; ;) { nativePollOnce(nextPollTimeoutMillis); synchronized (this) {
                // The current time
                final long now = SystemClock.uptimeMillis();
                
                Message msg = mMessages;
                if(msg ! =null) {
                    if (now < msg.when) {
                        // If the current time has not reached the processing time of the message, then the waiting time is calculated
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // The second message becomes the header
                        mMessages = msg.next;
                        msg.next = null;
                        mBlocked = false;
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                mBlocked = true; }}}// Sleep the calling thread of the next method for a specified time
    private void nativePollOnce(long nextPollTimeoutMillis) {

    }
Copy the code

Consider a situation where, while next() is still blocking, an external Message is inserted into the Message queue that can be processed immediately or has a short blocking wait. At this point, the dormant thread needs to be woken up, so enqueueMessage needs to add logic to determine whether the next() method needs to be woken up

	void enqueueMessage(Message msg, long when) {
        synchronized (this) {
            // Used to flag whether the next method needs to be woken up
            boolean needWake = false;         
            Message p = mMessages;
            MSG is used as the head of the list if the list is empty, or if the timestamp of the message at the head of the queue is larger than MSG
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;     
                // Need to wake up
                needWake = mBlocked;
            } else {
                Message prev;
                // Find the first message in the list whose timestamp is larger than MSG and insert MSG in front of the message
                for(; ;) { prev = p; p = p.next;if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }  
            if (needWake) {
                // Wake up the next() methodnativeWake(); }}}// Wake up the next() method
    private void nativeWake(a) {}Copy the code

3, the Handler

Now that MessageQueue has been identified as the place to store messages, it’s natural to have a class that can send messages to MessageQueue, let’s call it Handler. What does Handler do?

  • You want to be able to send Runnable messages in addition to messages of type Message. This is simple. The Handler internally wraps the Runnable as a Message
  • You want to send delayed messages to perform delayed tasks. This is also simple, using the when field inside Message to identify the timestamp when you want the task to execute
  • It is desirable to implement thread switching so that messages sent from child threads can be executed on the main thread and vice versa. A child thread can send a message to a particular mainMessageQueue, and then let the main thread loop to fetch the message from that queue and execute it.

So, Message definition and sending are handled by handlers, but Message distribution can be handed off to other threads

For Runnable to be wrapped as a Message, the processing logic for a Message needs to be defined by a Handler, so Message needs to add two more fields

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
public final class Message {
    // Unique identifier
    public int what;
    / / data
    public Object obj;
    / / timestamp
    public long when;
	// Next node
    public Message next;
	// Wrap Runnable as Message
    public Runnable callback;
    // Refers to the sender of the Message and is also the final handler of the Message
    public Handler target;
}
Copy the code

Handler needs to contain at least a few methods: a method for sending messages and Runnable, a handleMessage method for processing messages, and a dispatchMessage method for distributing messages

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
public class Handler {

    private MessageQueue mQueue;
    
    public Handler(MessageQueue mQueue) {
        this.mQueue = mQueue;
    }

    public final void post(Runnable r) {
        sendMessageDelayed(getPostMessage(r), 0);
    }

    public final void postDelayed(Runnable r, long delayMillis) {
        sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final void sendMessage(Message r) {
        sendMessageDelayed(r, 0);
    }

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

    public void sendMessageAtTime(Message msg, long uptimeMillis) {
        msg.target = this;
        mQueue.enqueueMessage(msg, uptimeMillis);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = new Message();
        m.callback = r;
        return m;
    }

    // Override this method externally to consume Message
    public void handleMessage(Message msg) {}// Used to distribute messages
    public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {
            msg.callback.run();
        } else{ handleMessage(msg); }}}Copy the code

The child thread can then use the Handler like this: Bind the Handler object held by the child thread to the mainMessageQueue associated with the main thread The main thread is responsible for looping the Message from the mainMessageQueue and then calling Handler’s dispatchMessage method to implement thread switching

        Handler handler = new Handler(mainThreadMessageQueue) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1: {
                        String ob = (String) msg.obj;
                        break;
                    }
                    case 2: {
                        List<String> ob = (List<String>) msg.obj;
                        break; }}}}; Message messageA =new Message();
        messageA.what = 1;
        messageA.obj = "https://github.com/leavesC";
        Message messageB = new Message();
        messageB.what = 2;
        messageB.obj = new ArrayList<String>();
        handler.sendMessage(messageA);
        handler.sendMessage(messageB);
Copy the code

4, which

Now let’s think about how the Handler gets the MessageQueue associated with the main thread and how the main thread gets the Message from the MessageQueue and calls back to the Handler. There must be a relay, let’s call it a Looper. What exactly does Looper need to do?

  • Each Looper object should have a unique MessageQueue and Thread instance so that threads and main threads can send messages to each other for processing
  • An infinite loop needs to be opened inside the Looper, and its associated thread is responsible for retrieving messages from the MessageQueue loop for processing
  • Since the main thread is special, consider storing the Looper object associated with the main thread as a static variable if it is directly available to the quilt thread

So you have the big picture of Looper. Each thread initializes its own Looper instance by using the prepare() method. MyLooper () method is used to retrieve the Looper object associated with the current thread. The sMainLooper associated with the main thread exists as a static variable for child threads to fetch

/ * * *@Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc: * GitHub:https://github.com/leavesC * /
final class Looper {

    final MessageQueue mQueue;

    final Thread mThread;

    private static Looper sMainLooper;

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private Looper(a) {
        mQueue = new MessageQueue();
        mThread = Thread.currentThread();
    }

    public static void prepare(a) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    public static void prepareMainLooper(a) {
        prepare();
        synchronized (Looper.class) {
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}public static Looper getMainLooper(a) {
        synchronized (Looper.class) {
            returnsMainLooper; }}public static Looper myLooper(a) {
        returnsThreadLocal.get(); }}Copy the code

Looper also needs to have a method that loops to get messages from MessageQueue and process them, call it loop(). It can also be as simple as looping out the Message from the MessageQueue and then distributing the Message back to the Handler

	public static void loop(a) {
        final Looper me = myLooper();
        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();// It may blockmsg.target.dispatchMessage(msg); }}Copy the code

The main thread initializes the sMainLooper by calling prepareMainLooper(), and then calls loop() to loop in the mainMessageQueue for processing. Without a message, the main thread is temporarily dormant. The child thread takes the sMainLooper and initializes the Handler with it. In this way, messages sent by the thread to the Handler are stored in the mainMessageQueue and consumed by the main thread

5. Summarize

The Message, MessageQueue, Handler, and Looper classes should all be clear. Different threads can then rely on each other’s Looper for cross-thread processing of messages

For example, for the following code, the handleMessage method is eventually called on mainThread, even though the Handler is initialized on the otherThread.

	    Thread mainThread = new Thread() {
            @Override
            public void run(a) {
                // Initialize mainLooper
                Looper.prepareMainLooper();
                // Start the loopLooper.loop(); }}; Thread otherThread =new Thread() {
            @Override
            public void run(a) {
                Looper mainLooper = Looper.getMainLooper();
                Handler handler = new Handler(mainLooper.mQueue) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1: {
                                String ob = (String) msg.obj;
                                break;
                            }
                            case 2: {
                                List<String> ob = (List<String>) msg.obj;
                                break; }}}}; Message messageA =new Message();
                messageA.what = 1;
                messageA.obj = "https://github.com/leavesC";
                Message messageB = new Message();
                messageB.what = 2;
                messageB.obj = newArrayList<String>(); handler.sendMessage(messageA); handler.sendMessage(messageB); }};Copy the code

Here’s a quick summary:

  • Message: Used to indicate the task to be executed
  • Handler: If the Handler is bound to the main thread’s MessageQueue, the Message sent by the child thread can be consumed by the main thread for thread switching, UI updating, etc
  • MessageQueue: Message queue. Messages sent by the Handler are not executed immediately. They need to be stored in MessageQueue according to Message priority (delay time), and then executed in sequence
  • Stars: Looper is used to loop messages from the MessageQueue and pass them to the Message Handler (the Message sender Handler itself) for consumption. Each Message has a target variable that points to the Handler. This associates a Message with its handler. Different threads send messages across threads by taking each other’s Looper objects

With that in mind, let’s take a look at the actual source code implementation

Handler source code

1. How to initialize Handler

There are seven constructors for Handler, and apart from the two deprecated ones and the three hidden ones, there are actually only two developers can use. No matter which constructor is used, the ultimate purpose is to complete the initialization of the four constants mLooper, mQueue, mCallback and mAsynchronous. It can also be seen that MessageQueue is initialized by Looper. Handler has a one-to-one relationship with both Looper and MessageQueue, which cannot be changed once initialized

Most developers will probably use Handler’s no-argument constructor, which has been marked deprecated in Android 11. Google officials prefer to do this by explicitly passing in a Looper object rather than implicitly using the current thread-associated Looper

Handler has a one-to-one relationship with both Looper and MessageQueue, but Looper and MessageQueue can have a one-to-many relationship with Handler, which will be discussed later

	@UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;

	// omit the other constructors

    / * * *@hide* /
    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) = =0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

2. How to initialize Looper

When the Handler is initialized, if no Looper is passed in by the externally called constructor, looper.myLooper () is called to retrieve the Looper object associated with the current thread, and MessageQueue is fetched from the Looper. If the Looper object is null, an exception is thrown. Can’t create handler inside thread that has not called Looper. Prepare () Looper is initialized by calling looper.prepare () before initializing the Handler

As you can see from the Looper class, myLooper() is a static method of the Looper class that simply takes the value from the sThreadLocal variable and returns it. SThreadLocal is initialized using the prepare(Boolean) method, and can only be assigned once. Repeated calls will throw an exception

As we know, ThreadLocal can maintain a single variable instance for different threads, so different threads can have a one-to-one relationship with different Looper objects

  	@UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

    /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */
    public static @Nullable Looper myLooper(a) {
        return sThreadLocal.get();
    }

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare(a) {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
             // Only one assignment is allowed
        	// An exception is thrown if the assignment is repeated
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
Copy the code

In addition, the Looper constructor is private and initializes two constant values: mQueue and mThread. This means that Looper is a one-for-one correspondence between MessageQueue and Thread, and cannot be changed after association

	@UnsupportedAppUsage
    final MessageQueue mQueue;

	final Thread mThread;    

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
Copy the code

In daily development, we often use Handler’s no-argument constructor to perform UI refresh operations, so we must use the Looper object associated with the main thread, corresponding to the static variable sMainLooper in the Looper class

    @UnsupportedAppUsage
    private static Looper sMainLooper;  // guarded by Looper.class

	// It is marked as deprecated because sMainLooper is automatically initialized by the Android system and should not be initialized by external initiators
    @Deprecated
    public static void prepareMainLooper(a) {
        prepare(false);
        synchronized (Looper.class) {
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}/** * Returns the application's main looper, which lives in the main thread of the application. */
    public static Looper getMainLooper(a) {
        synchronized (Looper.class) {
            returnsMainLooper; }}Copy the code

PrepareMainLooper () is used to initialize the Looper object for the main thread, which in turn is called by the Main () method of the ActivityThread class. The main() method is the starting point of the Java program, so when the application starts, the system automatically initializes the mainLooper in the main thread. The looper.loop () method has been called to enable the loop processing of messages, and various interaction logic in the application process (for example: Screen touches, list slides, etc.) are distributed in this loop

Since Android automatically initialises the main thread Looper, we can use Handler’s no-argument constructor directly to handle UI-related events in the main thread

public final class ActivityThread extends ClientTransactionHandler {
 
    public static void main(String[] args) {... stars. PrepareMainLooper (); ... stars. The loop ();throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code

3. Handler sends the message

Handler has dozens of methods for sending messages, most of which end up calling the sendMessageAtTime() method. UptimeMillis is the specific timestamp that Message is going to execute. If this timestamp is larger than the current time, it means that a delayed task is going to execute. If the mQueue is null, the exception Message is printed and returned directly, since the Message needs to be handled by MessageQueue

 	public boolean sendMessageAtTime(@NonNull 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);
    }
Copy the code

MSG. Target = this; target refers to the object that sends the message. The message sent by the Handler object to MessageQueue is processed by the Handler object

	private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // Send the message to MessageQueue
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

4, MessageQueue

MessageQueue receives messages through the enqueueMessage method

  • Since it is possible for multiple threads to send messages to a MessageQueue at the same timeenqueueMessageThere is definitely an internal need for thread synchronization
  • It can be seen that MessageQueue internally stores messages in a linked list structure (message.next), and its position in the Message queue is determined by the timestamp size of the Message
  • MMessages represents the first message in the message queue. If mMessages is empty (the message queue is empty), or if the timestamp of mMessages is larger than the timestamp of the new message, the new message is inserted into the header of the message queue; If mMessages is not empty, look for the first non-empty message in the message queue whose trigger time is later than the new message and insert the new message in front of it

At this point, a message queue sorted by timestamp size is complete, and the next thing to do is pull messages out of the message queue for processing

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {... MSG. MarkInUse (); msg.when = when; Message p = mMessages;// Is used to indicate whether the thread needs to be woken up
            boolean needWake;
            MSG is used as the head of the list if the list is empty, or if the timestamp of the message at the head of the queue is larger than MSG
            / / s = = 0 Handler is invoked sendMessageAtFrontOfQueue method, put the MSG queue head directly
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // If the current thread is asleep + queue header message is barrier message + MSG is asynchronous message
                // Then you need to wake up the thread
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                
                Message prev;
                // Find the first message in the list whose timestamp is larger than MSG and insert MSG in front of the message
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        // If there are asynchronous messages in the queue before MSG, there is no need to actively wake up
                        // Because the wake-up time is already set
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr ! = 0 because mQuitting is false.
            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

Now that you know how the Message is stored, let’s look at how MessageQueue fetches the Message and calls it back to Handler. Reading the message in MessageQueue corresponds to the next() method. The next() method internally opens an infinite Loop, which causes the Loop thread to hibernate and hang until the condition is met, if there are no messages in the queue or if the header message is not ready to be processed

	@UnsupportedAppUsage
    Message next(a) {...for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            // Hibernate and suspend the Loop thread
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! =null && msg.target == null) {
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null && !msg.isAsynchronous());
                }
                if(msg ! =null) {
                    if (now < msg.when) {
                        // The queue header has not reached the processing time. Calculate the waiting time
                        // 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

The next() method is looped through the loop() method of Looper, which is also an infinite loop and can only be broken out of if queue.next() returns null. Because the next() method might trigger a blocking operation, the loop() method would also block when there were no messages to process, and when MessageQueue had a new message, Which will handle the message in a timely manner and call the MSG. Target. DispatchMessage (MSG) method the message back to the Handler

	/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop(a) {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }...for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return; }, MSG. Target. DispatchMessage (MSG); ...}}Copy the code

Handler’s dispatchMessage method distributes processing messages to the outside world, which are processed in the following order:

  • If the Message is passedpost(Runnable)The Runnable object is called back directly
  • If a Callback object is passed to the Handler during initialization, it is passed to the Handler firsthandleMessageMethod returns true, terminating the process
  • The invocation HandlerhandleMessageMethod to process messages, which is externally overridden to define the business logic
	public void dispatchMessage(@NonNull Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}private static void handleCallback(Message message) {
        message.callback.run();
    }

    public void handleMessage(@NonNull Message msg) {}Copy the code

At this point, the entire Message distribution process is complete

5. Message barriers

The Android system uses a Barrier mechanism to ensure that some high-priority messages (asynchronous messages) are executed as quickly as possible. The general process is as follows: first send a barrier message to MessageQueue. When MessageQueue traverses the barrier message, it will judge whether there is an asynchronous message in the current queue. If there is, the synchronous message will be skipped first (all the messages sent by the developer actively belong to the synchronous message), and the asynchronous message will be executed first. This mechanism allows synchronous messages not to be processed until the asynchronous message has been executed

The async parameter in the constructor of the Handler is used to control whether the Message being sent is an asynchronous Message

    public class Handler {

		final boolean mAsynchronous;

		public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {ยทยทยท mAsynchronous = async; }private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            	long uptimeMillis) {
        	msg.target = this;
        	msg.workSourceUid = ThreadLocalWorkSource.getUid();
        	if (mAsynchronous) {
                // Set it to asynchronous
            	msg.setAsynchronous(true);
        	}
        	returnqueue.enqueueMessage(msg, uptimeMillis); }}Copy the code

When MessageQueue obtains the queue header Message, if the target object of the queue header Message is null, it knows that the Message belongs to the barrier Message, and then it traverses backwards to find the first asynchronous Message for processing first. This step is repeated each time the next() method is called until all asynchronous messages are processed

	@UnsupportedAppUsage
    Message next(a) {...for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! =null && msg.target == null) { // Target is a barrier message if it is null
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                    // Loop through to find the first asynchronous message after the barrier message for processing
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null&&! msg.isAsynchronous()); } ยทยทยท ยท} ยทยทยท}}Copy the code

We can actively send asynchronous messages by calling the setAsynchronous(Boolean async) method of the Message object. To actively send a barrier message, reflection calls MessageQueue’s postSyncBarrier() method, which is hidden from the system

The purpose of barrier messages is to ensure that high-priority asynchronous messages are processed first, and ViewRootImpl uses this mechanism to enable the View drawing process to be processed first

Exit the Looper loop

The Looper class itself has a method restriction. Except for the main thread, all MessageQueue associated with the child thread can exit the Loop, that is, quitAllowed only the main thread can be false

public final class Looper {
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static void prepare(a) {
        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(newLooper(quitAllowed)); }}Copy the code

MessageQueue supports two ways to exit the Loop:

  • If safe is true, only messages that have not been executed are removed. Messages whose timestamp is the current time are not removed
  • If safe is false, remove all messages
	void quit(boolean safe) {
        if(! mQuitAllowed) {//MessageQueue is not allowed to exit the loop
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                // Avoid repeated calls
                return;
            }
            mQuitting = true;
            if (safe) {
                // Remove only all messages that have not been executed, do not remove messages whose timestamp is equal to the current time
                removeAllFutureMessagesLocked();
            } else {
                // Remove all messages
                removeAllMessagesLocked();
            }
            // We can assume mPtr ! = 0 because mQuitting was previously false.nativeWake(mPtr); }}Copy the code

7, IdleHandler

IdleHandler is an internal interface to MessageQueue, which can be used to perform low-priority operations when the Loop thread is idle. The addIdleHandler method of MessageQueue submits the operations to be performed

    public static interface IdleHandler {
        boolean queueIdle(a);
    }

	private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) { mIdleHandlers.add(handler); }}public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) { mIdleHandlers.remove(handler); }}Copy the code

When executing the next() method, MessageQueue tries to iterate through mIdleHandlers to execute idleHandlers in turn if it finds that the current queue is empty or the queue header message needs to be delayed

	@UnsupportedAppUsage
    Message next(a) {...int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for(;;) {...synchronized (this) {...// If the queue header message mMessages is null or mMessages need to be deferred
                // Execute IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    / / IdleHandler execution
                    // If false is returned, it is no longer needed, so remove it
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}} ยทยทยท}}Copy the code

For example, ActivityThread adds a GcIdler to the main thread MessageQueue to try to perform GC operations when the main thread is idle

public final class ActivityThread extends ClientTransactionHandler {
    
    @UnsupportedAppUsage
    void scheduleGcIdler(a) {
        if(! mGcIdlerScheduled) { mGcIdlerScheduled =true;
            / / add IdleHandler
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
    
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle(a) {
            / / try to GC
            doGcIfNeeded();
            purgePendingResources();
            return false; }}}Copy the code

8. Summarize

So to sum up all of this

  1. Each Handler is associated with an instance of Looper, which can be passed in via the constructor when initializing the Handler, otherwise the Looper object associated with the current thread is used by default
  2. Each Looper is associated with an instance of MessageQueue that each thread needs to callLooper.prepare()Method to initialize a thread-specific instance of Looper and callLooper.loop()Method to cause the thread to loop back to the MessageQueue and execute the message. By default, the Android system initializes a Looper object associated with the main thread for each application and turns on a loop to handle the main thread messages by default
  3. MessageQueue stores messages in a link structure. Messages with earlier execution times (i.e., smaller timestamps) are placed at the head of the linked list. Looper loops messages out of the list and calls back to the Handler, which may involve blocking operations
  4. Message, Handler, Looper, and MessageQueue constitute a producer and consumer pattern. Message is the product, MessageQueue is the transport pipe, Handler is the producer, and Looper is the consumer
  5. Handler to Looper, Handler to MessageQueue, Looper to MessageQueue, and Looper to Thread are all one-to-one relationships. They cannot be changed after being associated. But Looper to Handler and MessageQueue to Handler can be one-to-many
  6. Handler can be used to update the UI with an implicit prerequisite: Handler is associated with the main thread Looper. Handler initialized in the main thread is associated with the main thread Looper by default, so ithandleMessage(Message msg)The method is called by the main thread. If the Handler initialized by the child thread also wants to perform UI updates, it needs to get the mainLooper to initialize the Handler
  7. For loopers we create ourselves in child threads, we should actively exit the loop when it is no longer needed, otherwise the child thread will never be released. For the main Loop, we should not actively exit, otherwise the application will crash
  8. We can add IdleHandler to MessageQueue to perform low-priority tasks while the Loop thread is idle. For example, if we have a requirement that we want to perform some UI operations after the main thread has finished drawing the interface, we can do this with IdleHandler, which will avoid slowing the user down to the first screen of the page

Handler application in the system

1, HandlerThread

HandlerThread is a class in the same package as Handler from the Android SDK. The name of the class indicates that it is a thread and uses Handler

The usage is similar to the following code. The Handler is initialized with a Looper object inside the HandlerThread, and the time-consuming task is declared in the Handler. The main thread triggers the HandlerThread to execute the time-consuming task by sending a message to the Handler

class MainActivity : AppCompatActivity() {

    private val handlerThread = HandlerThread("I am HandlerThread")

    private val handler by lazy {
        object : Handler(handlerThread.looper) {
            override fun handleMessage(msg: Message) {
                Thread.sleep(2000)
                Log.e("MainActivity"."Here are the child threads that can be used to perform time-consuming tasks:" + Thread.currentThread().name)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            handler.sendEmptyMessage(1)
        }
        handlerThread.start()
    }

}
Copy the code

The source code for HandlerThread is fairly simple, with just over 100 lines

HandlerThread, a subclass of Thread, is designed to execute time-consuming tasks. Its run() method automatically creates a Looper object for itself and stores it to mLooper. The HandlerThread will then loop through the Message

public class HandlerThread extends Thread {
    
    // Thread priority
    int mPriority;
    / / thread ID
    int mTid = -1;
    // The Looper object held by the current thread
    Looper mLooper;
    
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    @Override
    public void run(a) {
        mTid = Process.myTid();
        // Trigger the current thread to create a Looper object
        Looper.prepare();
        synchronized (this) {
            // Get the Looper object
            mLooper = Looper.myLooper();
            // Wake up all waiting threads
            notifyAll();
        }
        // Set the thread priority
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // Start the message loop
        Looper.loop();
        mTid = -1; }}Copy the code

In addition, HandlerThread contains a getLooper() method to getLooper. When we call handlerThread.start() externally to start the thread, the getLooper() method must wait until Looper is initialized before returning, because the execution timing of its run() method is still uncertain. Otherwise, it will block due to wait(). After the run() method has initialized Looper, notifyAll() is called to wake up all threads in the wait state. So before using a HandlerThread, outsiders remember to call the start() method to start the HandlerThread

    // Get the Looper object associated with the HandlerThread
    // Because getLooper() may be executed before run()
	// So when mLooper is null, the caller thread blocks until the Looper object is created
    public Looper getLooper(a) {
        if(! isAlive()) {return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
Copy the code

HandlerThread facilitates interaction between the main thread and child threads. The main thread can declare time-consuming tasks directly through the Handler and hand them off to the child thread to execute. Handlerthreads can also be easily shared between multiple threads. The main thread and other child threads can send tasks to the HandlerThread, and the HandlerThread can ensure that the execution of multiple tasks is orderly

2, IntentService

IntentService IntentService is a subclass of Service provided by the system. It is used to execute time-consuming tasks sequentially in the background. It stops automatically after all tasks are processed, without manually calling stopSelf(). And because IntentService is one of the four components that has a higher priority and is not easily killed by the system, it is suitable for performing some high-priority asynchronous tasks

IntentService has been officially recommended by Google for developers in the past, but has been deprecated in Android 11, but that doesn’t stop us from understanding how it works

IntentService internally relies on HandlerThread. Its onCreate() method creates a HandlerThread that takes the Looper object to initialize ServiceHandler. ServiceHandler passes each Message it receives to the abstract onHandleIntent method, which subclasses implement to declare time-consuming tasks

public abstract class IntentService extends Service {
    
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); }}@Override
    public void onCreate(a) {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        // Trigger HandlerThread to create Looper object
        thread.start();
		// Get the Looper object and build a Handler that can send messages to HandlerThread
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
    
}
Copy the code

Each time you start IntentService, the onStart() method is called, wrapping the Intent and startId as a Message object and sending it to the mServiceHandler. Of particular interest is the startId parameter, which uniquely identifies each task request to IntentService. The value of startId is automatically incremented each time the onStart() method is called back. An IntentService should not stop an IntentService immediately after it has processed a Message, because a MessageQueue has yet to be retrieved. The stopSelf(int) method will not cause IntentService to be stopped if the stopSelf(int) method is passed a parameter that is not equal to the current startId value, thus preventing the omission of unprocessed messages

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
Copy the code

Fourthly, the application of Handler in tripartite library

1, the EventBus

EventBus is a Publish /subscribe event bus for Android and Java. This shows that EventBus is universally available in Java environments, with special platform support for Android. EventBus’s four message sending policies include threadmode. MAIN to specify message callbacks on the MAIN thread, internally via a Handler

EventBusBuilder attempts to fetch MainLooper, which, if available, can be used to initialize HandlerPoster for the main thread callback

	MainThreadSupport getMainThreadSupport(a) {
        if(mainThreadSupport ! =null) {
            return mainThreadSupport;
        } else if (AndroidLogger.isAndroidLogAvailable()) {
            Object looperOrNull = getAndroidMainLooperOrNull();
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {
            return null; }}static Object getAndroidMainLooperOrNull(a) {
        try {
            return Looper.getMainLooper();
        } catch (RuntimeException e) {
            // Not really a functional Android (e.g. "Stub!" maven dependencies)
            return null; }}Copy the code
public class HandlerPoster extends Handler implements Poster {

    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper); ......}@Override
    public void handleMessage(Message msg) {...}}Copy the code

2, Retrofit

Like EventBus, Retrofit’s internal implementation does not rely on the Android platform and can be used with any Java client. Retrofit is just a special implementation of the Android platform

When building a Retrofit object, we can choose to pass a Platform object that marks the Platform on which the caller resides

public static final class Builder {
    
    private final Platform platform;
    
    Builder(Platform platform) {
      this.platform = platform; }...}Copy the code

The Platform class has only one unique subclass, the Android class. The main logic is to override the defaultCallbackExecutor() method of the parent class and use a Handler to call back network requests on the main thread

static final class Android extends Platform {
    
    @Override
    public Executor defaultCallbackExecutor(a) {
      return newMainThreadExecutor(); }...static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override
      public void execute(Runnable r) { handler.post(r); }}}Copy the code

5. Q&a session

1. Mapping between Handler, Looper, MessageQueue, and Thread

First of all, MessageQueue and Thread in Looper are constants, and Looper instances exist in ThreadLocal, which indicates that there is one-to-one correspondence between Looper and MessageQueue. A Thread is only associated with the same Looper object and MessageQueue object throughout its lifetime

public final class Looper {
 
   final MessageQueue mQueue;
   final Thread mThread;
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
   private Looper(boolean quitAllowed) {
        mQueue = newMessageQueue(quitAllowed); mThread = Thread.currentThread(); }}Copy the code

Looper and MessageQueue in Handler are also constants, indicating that Handler has a one-to-one relationship with Looper and MessageQueue. However, Looper and MessageQueue can have a one-to-many relationship with handlers, for example, handlers declared in more than one child thread can be associated with mainLooper

public class Handler {
    
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    
}
Copy the code

2. Synchronization mechanism of Handler

When MessageQueue stores messages, the enqueueMessage method has a synchronization lock inside it to avoid race problems caused by multiple threads sending messages simultaneously. In addition, the next() method also has a synchronization lock inside it, so Looper can distribute messages in an orderly manner. Most importantly, Looper is always iterated by a specific thread, so there is no race when consuming a Message

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this{...})return true;
    }


    @UnsupportedAppUsage
    Message next(a) {...for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {ยทยทยท} ยทยทยท}}Copy the code

3. Handler sends a synchronization message

What if a child thread sends a message to the main thread via Handler and wants to wait until the message completes before the child thread continues? In fact, such problems involving multi-thread synchronous waiting are often implemented by relying on thread sleep + thread wake up mechanism

Handler itself provides a runWithScissors method that can be used to do this, but it is hidden and cannot be called directly. RunWithScissors first determines if the target thread is the current thread, if so Runnable is executed, otherwise BlockingRunnable needs to be used

    / * * *@hide* /
    public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }

        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }

        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }
Copy the code

BlockingRunnable also has simple logic. It calls wait() to block the sender thread until the Runnable is finished. NotifyAll () is used to wake up the sender thread when the Runnable is finished. This allows the sender thread to wait until the Runnable is complete

private static final class BlockingRunnable implements Runnable {
    
        private final Runnable mTask;
    	// Is used to mark whether the mTask has finished
        private boolean mDone;

        public BlockingRunnable(Runnable task) {
            mTask = task;
        }

        @Override
        public void run(a) {
            try {
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true; notifyAll(); }}}public boolean postAndWait(Handler handler, long timeout) {
            if(! handler.post(this)) {
                return false;
            }

            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while(! mDone) {long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {
                            // Time limit wait
                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while(! mDone) {try {
                            // Wait indefinitely
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true; }}Copy the code

Although the runWithScissors method cannot be called directly, we can also rely on this idea to implement BlockingRunnable ourselves. This method is not safe, however. If the Loop exits unexpectedly and the Runnable cannot be executed, the suspended thread cannot be woken up. Use caution

Handler to avoid memory leaks

When exiting an Activity, a memory leak can occur if the Handler, an inner class, holds delayed messages waiting to be processed. By calling the Handler. RemoveCallbacksAndMessages (null) to remove the Message needs to be handled at the

This method removes all message. obj = token messages from the Message queue, or removes all messages if the token is null

    public final void removeCallbacksAndMessages(@Nullable Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }
Copy the code

5. How to reuse Message

Messages are created frequently on Android because there are many events that need to be delivered to mainLooper by Message. Message provides MessagePool for Message cache reuse to optimize memory usage in order to reduce the number of messages that are repeatedly created

After Looper consumes the Message, it will call the recycleUnchecked() method to recycle the Message. After all resources are cleared, the Message will be cached to the sPool variable, and the cached Message will be set to the next node, next. Up to 50 messages are cached using this linked list structure. The meta design pattern is used here

The obtain() method determines whether a cache is currently available, removes sPool from the list and returns it, or returns a new Message instance otherwise. Therefore, when sending messages, we should try to obtainMessage instances by calling message.obtain () or handler.obtainMessage () methods

public final class Message implements Parcelable {
    
    / * *@hide* /
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    
    public static Message obtain(a) {
        synchronized (sPoolSync) {
            if(sPool ! =null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                returnm; }}return new Message();
    }
    
    @UnsupportedAppUsage
    void recycleUnchecked(a) {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this; sPoolSize++; }}}}Copy the code

6. Problems of Message reuse mechanism

Because Message uses a cache reuse mechanism, there is a Message invalidation problem. When the handleMessage method is called back, all arguments carried by the Message are empty, whereas if the external handleMessage method uses an asynchronous thread to process the Message, the asynchronous thread will get a blank Message

val handler = object : Handler() {
    override fun handleMessage(msg: Message) {
        handleMessageAsync(msg)
    }
}

fun handleMessageAsync(msg: Message) {
    thread {
        // You get a blank Message object
        println(msg.obj)
    }
}
Copy the code

7. How does Message increase priority

Handler contains a sendMessageAtFrontOfQueue method can be used to improve the processing of the Message priority. This method sets the timestamp to 0 for Message so that Message can be inserted directly into the header of the MessageQueue to give priority to processing. This method is not officially recommended, however, because in the most extreme cases it can lead to other messages remaining unprocessed or other unexpected situations

    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        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, 0);
    }
Copy the code

8. Check Looper’s Message distribution efficiency

As Looper loops through the Observer, it calls back the callback event for each Message. In addition, if the slowDispatchThresholdMs and slowDeliveryThresholdMs thresholds are set, the time and time of Message distribution will be monitored, and the Log will be printed if there is an anomaly. This mechanism can be used for application performance monitoring to detect potential Message processing exceptions, but unfortunately the monitoring method is hidden by the system

	public static void loop(a) {
        finalLooper me = myLooper(); ...for (;;) {
            Message msg = queue.next(); // might block...// The distribution event used to call out the notification Message
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            // If Looper sends messages later than the preset time and exceeds the threshold, Looper is considered to be sending messages too slowly
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            // If the processing time of outgoing messages exceeds this threshold, the external processing is considered too slow
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if(traceTag ! =0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            // Start time to distribute Message
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            // The time when the distribution ends
            final long dispatchEnd;
            Object token = null;
            if(observer ! =null) {
                // Start sending messages
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if(observer ! =null) {
                    // Message is distributed without throwing an exception
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if(observer ! =null) {
                    // An exception was thrown when Message was distributed
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if(traceTag ! =0) { Trace.traceEnd(traceTag); }}if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        // If the Message is delivered later than the scheduled time and more than 10 milliseconds apart, it is considered late delivery
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false; }}else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true; }}} ยทยทยท}}Copy the code

Where is the main thread Looper created

Created by the Main () method of the ActivityThread class. The main() method is the starting point of the Java program. When the application starts, the system automatically initializes the mainLooper in the main thread. The looper.loop () method has been called to enable the loop processing of messages. Screen touches, list slides, etc.) are distributed in this loop. Since Android automatically initialises the main thread Looper, we can use Handler’s no-argument constructor directly to handle UI-related events in the main thread

public final class ActivityThread extends ClientTransactionHandler {
 
    public static void main(String[] args) {... stars. PrepareMainLooper (); ... stars. The loop ();throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code

When does the main thread Looper exit the loop

When the Handler inside ActivityThread receives the EXIT_APPLICATION message, it exits the Looper loop

		public void handleMessage(Message msg) {
            switch (msg.what) {
                case EXIT_APPLICATION:
                    if(mInitialApplication ! =null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break; }}Copy the code

11. Why does the main thread looper.loop () not cause ANR

This question is very common on the Internet, and I thought it was very strange when I first saw it. Why should the main thread have ANR? The question itself feels deliberately designed to mislead

Look at the following example. In the case of the doSomeThing() method, which is placed after the for loop, the main thread is indeed blocked, causing the method to never execute. However, for an application, all operations in the main thread are actually placed in the for loop, which is always executed, so it doesn’t matter if it is an infinite loop. Therefore, for an application, the main thread is not blocked, which naturally does not cause ANR. In addition, when there is no message to be processed in MessageQueue, the main thread is suspended by epoll mechanism to avoid it occupying CPU resources all the time

    public static void main(String[] args) {
        for(; ;) {// The main thread executes....
        }
        doSomeThing();
    }
Copy the code

So in the Main method of ActivityThread, no meaningful code is declared after the message loop is turned on. Normally, an application does not exit the loop, and if it does, it will simply throw an exception

	public static void main(String[] args) {... stars. PrepareMainLooper (); ... stars. The loop ();throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

So the loop loop itself does not cause ANR, but ANR occurs because Message processing takes too long within the loop

12, Child thread must not be able to play Toast

You can’t pop a Toast directly in a child thread, but you can. Since Toast’s constructor calls for a Looper object, if no non-null Looper instance is passed to the constructor argument, we try to use the Looper object associated with the caller’s thread, and throw an exception if none is available

    public Toast(Context context) {
        this(context, null);
    }

	public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mToken = new Binder();
        looper = getLooper(looper);
        mHandler = newHandler(looper); ...}private Looper getLooper(@Nullable Looper looper) {
        if(looper ! =null) {
            return looper;
        }
        // looper.mylooper () throws an exception if null
        return checkNotNull(Looper.myLooper(),
                "Can't toast on a thread that has not called Looper.prepare()");
    }
Copy the code

To pop Toast in the child thread, you need to actively create a Looper object for the child thread and start the loop. However, this method will result in the child thread never being able to exit the loop, and will need to actively exit the loop through looper.mylooper ().quit()

    inner class TestThread : Thread() {

        override fun run(a) {
            Looper.prepare()
            Toast.makeText(
                this@MainActivity."Hello: " + Thread.currentThread().name,
                Toast.LENGTH_SHORT
            ).show()
            Looper.loop()
        }

    }
Copy the code

Child threads must not update the UI. The main thread must work, right?

Android only limits the creation and update of the View wrootimPL to the same thread, not the main thread

Updating the UI even on the main thread can cause an application to crash if not used properly. For example, if you trigger the creation of the ViewRootImpl with show+hide in the subthread, and then try to display the Dialog in the main thread, you will find that the program crashes

class MainActivity : AppCompatActivity() {

    private lateinit var alertDialog: AlertDialog

    private val thread = object : Thread("hello") {
        override fun run(a) {
            Looper.prepare()
            Handler().post {
                alertDialog =
                    AlertDialog.Builder(this@MainActivity).setMessage(Thread.currentThread().name)
                        .create()
                alertDialog.show()
                alertDialog.hide()
            }
            Looper.loop()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            alertDialog.show()
        }
        thread.start()
    }

}
Copy the code
    E/AndroidRuntime: FATAL EXCEPTION: main
    Process: github.leavesc.test, PID: 5243
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6892)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.setFlags(View.java:11478)
        at android.view.View.setVisibility(View.java:8069)
        at android.app.Dialog.show(Dialog.java:293)
Copy the code

The ViewRootImpl will save the current thread to the mThread during initialization, and call the checkThread() method to check the thread during subsequent UI update. If multiple threads are found, the above exception information will be directly thrown

public final class ViewRootImpl implements ViewParent.View.AttachInfo.Callbacks.ThreadedRenderer.DrawCallbacks {
            
     final Thread mThread;       
            
     public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) { mThread = Thread.currentThread(); ...}void checkThread(a) {
        if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views."); }}}Copy the code

14. Why a single-threaded UI architecture

In fact, this is easy to understand, is to improve operational efficiency and reduce the difficulty of implementation. If multiple threads are allowed to access the UI concurrently, many even small local refresh operations (such as TextView.settext) will require synchronization locks to avoid races, which will increase the “cost” of UI refresh operations and reduce the overall application performance. It also causes Android’s UI architecture to be forced to “defend” against multithreaded environments, even if the developer keeps updating the UI on the same thread, which makes the system more difficult to implement

Therefore, the simplest and most efficient way to access the UI is to use the single-threaded model

How do I send tasks across threads

In general, communication between two threads is cumbersome and requires a lot of thread synchronization. With Looper, we can deliver tasks across threads in a simpler way

If you look at the following code, you can see from the thread name that pops up after TestThread runs that Toast is popped up at Thread_1. If you think of Thread_2 as the main thread, the following code is equivalent to sending a time-consuming task from the main thread to a child thread. This implementation is similar to the HandlerThread class provided by Android

	inner class TestThread : Thread("Thread_1") {

        override fun run(a) {
            Looper.prepare()
            val looper = Looper.myLooper()
            object : Thread("Thread_2") {
                override fun run(a) {
                    val handler = Handler(looper!!)
                    handler.post {
                        // The output is Thread_1
                        Toast.makeText(
                            this@MainActivity,
                            Thread.currentThread().name,
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            }.start()
            Looper.loop()
        }

    }
Copy the code

16, how to determine whether the current is the main thread

Judge by Looper

        if (Looper.myLooper() == Looper.getMainLooper()) {
            // is the main thread
        }

        if (Looper.getMainLooper().isCurrentThread){
            // is the main thread
        }
Copy the code