To better understand how Looper works, we need to understand ThreadLocal. If we don’t know about ThreadLocal, we can refer to the Principle of ThreadLocal

An overview of the

Handler as a daily development requirement, it is inevitable that this knowledge will be involved. From the developer’s point of view, The Handler is the upper interface to the Android messaging mechanism, making development only need to interact with the Handler. Handler is also simple to use, making it easy to switch a task to the thread where the Handler is running.

Many people think of Handler as just updating the UI, which is true, but updating the UI is just a special use case for Handler. In particular, it is sometimes necessary to perform time-consuming operations on child threads, such as network access or time-consuming I/O operations, and when these time-consuming operations are completed, the UI of the program changes accordingly. Due to the limitations of android development specification, UI controls cannot be accessed in child threads, because UI controls are thread unsafe. In this case, the Handler can switch the UI update operation to the main thread.

The Android messaging mechanism mainly refers to the operation mechanism of the Handler. In fact, Handler, Looper and MessageQueue emerged from a set of operating systems. MessageQueue is a Message queue. ** provides insertion and deletion in the form of queues, mainly used for Message storage, and the internal implementation uses the form of single linked list to organize messages. ** Looper is used to process messages, and inside Looper an infinite loop checks to see if there are any new messages, processes them, and waits for none. In particular, Looper is implemented using ThreadLocal. Since ThreadLocal can access data in each thread without interfering with each other, it is easy to obtain Looper for each thread through ThreadLocal.

Message: Android.os. Message is defined as a Messge containing the necessary description and attribute data, and this object can be sent to Android.os. Handler for processing. Property fields: arg1, arg2, WHAT, obj, replyTo, etc. Arg1 and arg2 are used to store integer data. What is used to hold message identifiers; Obj is any Object of type Object; ReplyTo is the message manager and is associated with a handler, which processes the messages within it. Message objects are not usually new directly, just call the obtainMessage method in the handler to get the Message object directly.

Note that threads do not have Looper by default. If you want to use a Handler, you must create Looper for the thread, but the main thread of the APP, the ActivityThread, will initialize Looper when the main thread is created. Also, creating a Handler on a thread without Looper will fail.

Use case

Without further ado, above example:

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private String TAG = "MainActivity";
    private int i = 0;

    Handler mHandler = new Handler@param MSG */ @override public void handleMessage(Message MSG){ super.handleMessage(msg);if(msg.what==1){
                textView.setText(msg.arg1+""); }}}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); } public void onClick(View v){ ++i; // Create a new threadThread(){
            @Override
            public void run() {
                super.run();
                doSendMsg(); } }.start(); } /** ** do a time-consuming operation in the child thread, and when done, notify the Handler to update the UI */ private voiddoSendMsg(){ try { Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace(); } Message message = Message.obtain(); message.arg1 = i; message.what = 1; mHandler.sendMessage(message); }}Copy the code

The principle of analysis

Code version: Android API25

Message

Generate a Message

Messages are usually not new. Instead, we get a new Message by calling Handler’s obtainMessage() :

 public final Message obtainMessage()
    {
        return Message.obtain(this);
    }
Copy the code

Handler calls Message’s obtain(Handler h) method:

public final class Message implements Parcelable { public int what; Public int arg1; Public int arg2; public Object obj; Public Messenger replyTo; // A reply to this message can be sent. Public int sendingUid = -1; /*package*/ static final int FLAG_IN_USE = 1 << 0; /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /*package*/ int flags; /*package*/ long when; // Message execution time is important!! /*package*/ Bundle data; /*package*/ Handler target; // target handles the message /*package*/ Runnable callback; // callback of Runnable type /*package*/ Message next; Private static final Object sPoolSync = new Object(); Private static Message sPool; Private static int sPoolSize = 0; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50; private static boolean gCheckRecycle =true;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if(sPool ! = null) { Message m = sPool; // Assign the header to Message sPool = m.next; // update the header to the next node m.ext = null; The original node is not connected with the original one m.F lags = 0; // clearin-use flag sPoolSize--; // Reduce the number of recycle pools by onereturnm; }}returnnew Message(); } public static Message obtain(Handler h) { Message m = obtain(); m.target = h; // Notice that the Handler is stored in target, which will be called later to handle the Messagereturn m;
    }

// ...
}
Copy the code

Finally, the Message obtain() method returns a new Message instance from the global pool. Allows us to avoid assigning new objects in many cases. The global pool is a singly linked list that takes a Message from the top node, and if it doesn’t have one, it creates a new Message. The single linked list is a collection of messages that need to be recycled. The global Pool uses static constants to organize useless messages in a single linked list.

As you can see from the source code, multiple obtain methods are overloaded, which simply configure these optional parameters and then call obtain() to obtain a Message.

Recovery of the Message

If the generation is a bit confusing, let’s take a look at the collection in front of us.

    public void recycle() {
        if (isInUse()) {           // isInUse() return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return; // Unchecked(); /** * Recycles a Message that may bein-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains inthe recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; // Set it to what = 0; Arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) {if(sPoolSize < MAX_POOL_SIZE) { next = sPool; // Set the next Message to the previous header sPool = this; // update the header to the current node sPoolSize++; } // If MAX_POOL_SIZE is reached, the Message will be collected by GC because it is not connected to the reference chain}}Copy the code

Recycling is very simple. Check to see if it’s in use. If not, erase the data and add Message to what I call the recycling pool. Note that if the number of collection pools reaches the upper limit, the Message will be collected by the GC because it is not connected to the reference chain.

MessageQueue

MassageQueue, called a message queue, maintains a list of messages through a singly linked list data structure, and singly linked lists have advantages over insertions and deletions. MessageQueue consists of two operations: insert and read. The corresponding insert and read methods are enqueueMessage(Message MSG, long when) and next().

The working process

The workflow here first describes the workflow in the example and then adds some necessary processes such as recycling.

UI thread Looper creation

We know that UI threads are created in ActivityThread, and this function is the entry point to the entire APP. Next up is the main in ActivityThread:

public static void main(String[] args) { ... Looper.prepareMainLooper(); Looper ActivityThread thread = new ActivityThread(); thread.attach(false);

        if(sMainThreadHandler == null) {// Handle // getHandler() returns activityThread.h (extends Handler); sMainThreadHandler = thread.getHandler(); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); //2. Execute the message loop throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

See, with one or two steps of configuration, the Looper is actually running in the UI thread, and you’re ready to use handlers in your application. Child threads are not configured by default.

PrepareMainLooper () is a method that UI threads use to prepare Looper. PrepareMainLooper () is a public method that UI threads use to prepare Looper. We can use prepare() for Looper.

prepareMainLooper()

Let’s analyze it step by step (Looper source code 🙂

    public static void prepareMainLooper() {
        prepare(false); Synchronized (looper.class) {synchronized (looper.class) {synchronized (looper.class) {if(sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } // Put the UI thread's Looper into the static constant sMainLooper, so that the main thread can be generated whenever and wherever new Handler sMainLooper = myLooper(); // myLooper()return sThreadLocal.get();
        }
    }

    public static void prepare() {
        prepare(true); } private static void prepare(Boolean quitAllowed) {// Trueif the message queue can be quit.
        if(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // sThreadLocal is static final ThreadLocal<Looper> sthreadLocal. set(new Looper(quitAllowed)); } public static @Nullable LoopermyLooper() {
        returnsThreadLocal.get(); // Get the current thread's Looper}Copy the code

New a Looper and place it in the ThreadLocalMap of the main thread. So what did you do when you were new?

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

The message queue for the UI thread was created in new Looper and cannot be deleted.

    // True if the message queue can be quit.
    private final boolean mQuitAllowed;

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
Copy the code

Whether the queue can be deleted, passed all the way down, ended up being saved here. But there’s another Native method, what does it do? I think it’s a reference to the thread in which the object resides.

At this point, the first step in ActivityThread is complete. Since the use of the sMainThreadHandler is complicated, I will leave it to the next section. Instead of using the Handler in the example, I will execute the same Handler. But ActivityThread.H (sMainThreadHandler) encapsulates something else.

Create a Handler

Handler () {Handler ();} Handler ();

public class Handler {
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
       ...
        mLooper = Looper.myLooper();
        if(mLooper == null) {// *** important! throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }}Copy the code

We see that the Handler in the constructor gets the Looper of the current thread (UI thread) via looper.mylooper () and stores it in the local final variable mLooper.

Remember that ** message queues are encapsulated in loopers, and each Looper is associated with a thread (encapsulated by ThreadLocal), which ultimately equals that each message queue is associated with a thread. ** As you can see from the above code, each Handler is also associated with a message queue. Note here that Looper and MessageQueue are not associated with Handler, but rather Handler is associated with Looper and MessageQueue.

Looper.loop()

After Looper is created, we need to execute the message loop. We know that the Handler will Post the message to the message queue. That’s the initial looper.loop () in step 2.

    public static void loop() { final Looper me = myLooper(); // Get the current thread's Looperif (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 thelocal process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for(;;) {// 2. Message MSG = queue.next(); // might block might block 3if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            final long traceTag = me.mTraceTag;
            if(traceTag ! = 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally {if(traceTag ! = 0) { Trace.traceEnd(traceTag); } } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); msg.recycleUnchecked(); }}Copy the code

We can see that the loop method actually creates an infinite loop, and then fetches messages one by one from the message queue and processes them until null is reached before exiting. ** there is no blocking, so I am finished with the message exit? No, see MassageQueue’s next() for the principle:

 Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            returnnull; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; // Note that the following code is in the body of the loopfor (;;) {
            if(nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); // Important!! synchronized (this) { // Try to retrieve the next message. Returniffound. final long now = SystemClock.uptimeMillis(); // Message prevMsg = null; // Message MSG = mMessages; // Header // If there is no Handler, the next synchronized Message in the list is executed.if(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {// If the header is not nullif(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; // Retrieve the Message from the Message queueif(prevMsg ! // prevmsg.next = msg.next; // See next() figure 1}else{ mMessages = msg.next; } msg.next = null; // Disconnect the queue from the Message to be processedif (DEBUG) Log.v(TAG, "Returning message: "+ msg); msg.markInUse(); // mark as usedreturnmsg; }}elseNextPollTimeoutMillis = -1; // No more messages. NextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled.if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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); Handlers Run the idle handlers. // We only ever reach this code block during the first iteration.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 {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so wedo not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

    // Disposes of the underlying message queue.
    // Must only be called on the looper thread or the finalizer.
    private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }
Copy the code

The code is a long one, but we still have to look at it. It’s just a simple matter of fetching a single linked list of headers, modifying the corresponding pointer, and returning the retrieved headers. Since this is an infinite loop, one might wonder: is this loop particularly CPU intensive? It doesn’t. If messageQueue has a message, it continues to fetch the message; ** If there are no more messages, the thread will block in the nativePollOnce() method of the next() method, and the main thread will release CPU resources and go to sleep until the next message arrives or a transaction occurs (the nextPollTimeoutMillis set has arrived). Wake up the main thread by writing data to the write end of the PIPE pipe. Epoll is an IO multiplexing mechanism that monitors multiple descriptors at the same time. When a descriptor is ready (read or write ready), it immediately notifies the corresponding program to read or write operations, essentially synchronizing I/O, that is, reading or writing is blocked.

Summary: Looper object (message queue wrapped in Looper object) is created by looper.loop () and stored in sThreadLocal, and message loop is executed by looper.loop ().

Take, of course, followed by distribution. We call the MSG. Target. DispatchMessage (MSG) to carry out the Message. During the creation of a Message, references to the Handler passed in are stored in the Message (code and explanation at the top). Let’s look at Handler handling:

    final Callback mCallback;

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

    public void handleMessage(Message msg) {
    }
    
    public void dispatchMessage(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(); }}}Copy the code

This is also very simple, as we all know that we use Runnable when handling post() messages, while sendMessage() is a Message we build ourselves. Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler If you don’t use the default handleMessage(MSG), this method will be overridden most of the time, as in the example.

Here we can see that the priority of the three methods when distributing messages is as follows:

  • The Message callback method has the highest priority, message.callback.run();
  • The callback Handler method of assigning priorities that Handler. MCallback. HandleMessage (MSG);
  • Handler’s default method has the lowest priority, handler.handleMessage (MSG).

Use Handler to sendMessage(Message MSG)

Since we’re talking about POST () and sendMessage(), here’s how it adds a Message to the queue.

SendMessage () : sendMessage(); sendMessage() : sendMessage();

    /**
     * Pushes a message onto the end of the message queue after all pending messages
     * before the current time. It will be received in {@link #handleMessage},
     * in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessage(Message msg)
    {
        returnsendMessageDelayed(msg, 0); Public final Boolean sendMessageDelayed(Message MSG, long delayMillis) {public final Boolean sendMessageDelayed(Message MSG, long delayMillis) {if(delayMillis < 0) {// Check the delay because the delay can only be >= 0 delayMillis = 0; } // systemclock. uptimeMillis() the number of milliseconds since the start of the phone (not counting the time the phone slept) // systemclock. uptimeMillis() + delayMillis is calculated as the update timereturnsendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public Boolean sendMessageAtTime(Message MSG, long uptimeMillis) {// This value is assigned from Looper, This means that mQueue, which holds the same reference as Looper, will also be modified. // When looper.quit () is called, this value will be null. MessageQueue queue = mQueue;ifRuntimeException e = new RuntimeException(this +) {queue == null) {// Cannot be null because it needs to be sent to MassageQueue" sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false; // Insert failed}returnenqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; // Save the target of Messageif (mAsynchronous) {
            msg.setAsynchronous(true);  
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

All the way down to the bottom with three methods, fortunately all simple, take a look. EnqueueMessage (MSG, uptimeMillis); enqueueMessage(MSG, uptimeMillis) How did you add it? EnqueueMessage (Message MSG, long when) for MessageQueue

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if(msg.isinuse ()) {// a new Message is not InUse and is set to throw New IllegalStateException(MSG +) when it is recycled" This message is already in use."); } synchronized (this) {// obtain its own synchronized lockif(McOntract) {// MessageQueue quitting IllegalStateException e = new IllegalStateException(msg.target +)" sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); // Reclaim the Message instancereturn false; } msg.markInUse(); / / flags | = 1 mark are using MSG. The when = the when; // Set the execution time to insert Message. Message p = mMessages; // mMessages header Boolean needWake; // Whether the thread needs to be woken upif (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue ifblocked. msg.next = p; mMessages = msg; needWake = mBlocked; // whether the thread blocks}else {
                // Inserted within the middle of the queue.  Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); // Remember that the default is false Message prev; for (;;) Prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { 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); // Wake up thread Native method}} return true; }Copy the code

Looking at the implementation of enqueueMessage, its main operation is the insertion of a single linked list in the order of execution time when. Again, when is converted to the number of milliseconds between startup and execution.

  1. Check. Targe is used to process the Message, so it must have a target. Targe is used to process the Message, so it must have a target. Then verify that the current MessageQueue has been quit (McOntract), which is followed by an official insert.
  2. Configuration. Set the properties of Message, msg.markinuse (); msg.when = when;
  3. This is a clever way to solve several specific cases in a single piece of code, but they all boil down to:Insert the node before the header. Notice here that if you insert a Message that needs to be undelayed and the thread is blocked, it will be callednativeWake(mPtr)Wake up the thread.
  • When there is no list (p == 0)
  • The execution time of the message earlier than the message inside (the when = = 0 | | the when < p.w hen)
  1. Insert in the middle or at the end of the list. Loop through the list, get the appropriate Message from the list, and insert the new Message. The thread is not woken up because it has not reached message processing time. The insertion process is as follows:

Use Handler to post()

Let’s start with an example in use:

private Handler mHandler; @override protected void onCreate(Bundle savedInstanceState) {mHandler = new Handler(); new Thread(newRunnable() {
                @Override
                public void run() { try { Thread.sleep(1000); // There is a time consuming operation in the child thread, such as the request network mhandler. post(new)Runnable() {
                            @Override
                            public void run() {
                                mTestTV.setText("This is post"); // Update UI}}); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }Copy the code

This is a simple example, but I won’t explain it. Let’s see how this Handler handles the post:

    /**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is 
     * attached. 
     *  
     * @param r The Runnable that will be executed.
     * 
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r)
    {
       returnsendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); // Get a new Message m.callback = r; // The post Runnable saves this parameterreturn m;
    }
Copy the code

Sharp-eyed children’s shoes are probably easy to see. SendMessageDelayed (Message MSG, Long delayMillis) was called at the beginning of the post, doesn’t that look familiar? Yes, sendMessage(Message MSG) was called at the beginning of the post. This is the insertion of an undelayed message. Please refer to the previous article for details on how to insert.

Also, what if postDelayed(Runnable R, Long delayMillis) was used? The Handler mechanism is used to communicate with threads. The Handler mechanism is used to communicate with threads. Switch back to Handler’s postDelayed:

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
Copy the code

The sendMessageDelayed method is still called, but the delay is no longer the default 0. This is where delayed message insertion occurs.

Exit message loop

There are two ways to exit the message loop: Looper’s quit() and quitSafely()

   public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
Copy the code

Qiut (Boolean safe) for MassageQueue

    void quit(boolean safe) {
        if(! MQuitAllowed) {// The main thread setting is not allowed to exit. Other child threads are set by defaulttrue
           throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if(McOntract) {// Defaults tofalse, indicating whether the message is exiting the queuereturn;
            }
            mQuitting = true; // Globally unique assignment indicating that the message is exiting the queueif (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
Copy the code

First determine if it is the main thread, since the message queue of the main thread is not allowed to exit, and then determine if the current thread is exiting. Note that McOntract is a member variable of MassageQueue and has a default value, which is false. If the message is not exiting the queue, mark it as exiting. Note that this is the only global place where the McOntract value has been changed. Then determine whether it is safe to remove MessageQueue as required.

    private void removeAllMessagesLocked() { Message p = mMessages; / / head nodewhile(p ! = null) { Message n = p.next; // Next node p.ricycleunchecked (); P = n; p = n; p = n; } mMessages = null; // MessageQueue saves the header to null} private voidremoveAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); // The number of milliseconds since the phone was turned on (not counting sleep time); Message p = mMessages; / / head nodeif(p ! = null) {// If the latency of the header is greater than the current time // note that the comparison is in milliseconds from startup to now // this means that the set execution time has not been reachedif(p.when > now) { removeAllMessagesLocked(); // Remove all messages before execution time}else {
                Message n;
                for(;;) { n = p.next; // Get the next nodeif(n == null) {// End flag: no next onereturn;   
                    }
                    if(n.hen > now) {// The set execution time has not been reached, exit the loopbreak; } p = n; } // p < p style = "max-width: 100%;do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while(n ! = null); }}}Copy the code

RemoveAllMessagesLocked () directly retrieves all messages from MessageQueue. Either a delayed message (which is a message sent via sendMessageDelayed or via methods such as postDelayed) or a non-delayed message (delayMillis == 0).

RemoveAllFutureMessagesLocked () method, is the only empty MessageQueue messages all delays in the pool, and the message pool all the distributed delay message out to let the Handler to deal with, in this method, characterized by direct return, Then because the queue has a Message, the corresponding dispatchMessage(MSG) is called.

The quitSafely alternative to the quit method is that it will send all non-delayed messages before emptying the message.

The interview questions

How does looper start when handler sends a message to the child thread?

To send a message is to push a message into a message queue. Looper is already started when it is applied, polling for messages from the message queue.


First look at the following code:

        new Thread(){
            Handler handler = null;

            @Override
            public void run() {
                handler = new Handler();
            }
        }.start();
Copy the code

As mentioned earlier, Looper objects are ThreadLocal, that is, each thread has its own Looper, which can be null. However, when you create a Handler object in a child thread, an exception will be thrown if Looper is null. Source code to explain:

public class Handler {
       ...
   public Handler(Callback callback, boolean async) {
       ...
       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; }... }Copy the code

From the above program, we can see that an exception is thrown when the mLooper object is empty. This is because the Looper object in the thread has not yet been created, so sthreadLocal.get () returns null. The Handler principle is to associate with MassageQueue and deliver the message to MassageQueue. If MassageQueue does not exist, then there is no need for the Handler to exist. The MassageQueue is encapsulated in a Looper, so the Looper must not be empty when the Handler is created. Solutions:

     new Thread(){
            Handler handler = null;
            @Override
            public void run() { Looper.prepare(); // 1. Create a Looper for the current thread and bind it to ThreadLocal handler = new handler (); Looper.loop(); // 2. Start the message loop}}.start();Copy the code

The reason you can use it directly in the UI thread is because the ActivityThread does one or two steps for you by default.


Handler why loop is an infinite loop.

On Android, an ANR (Application Not Responding) exception is raised if a time-consuming operation is performed on the main thread (UI thread). ANR can be generated for one of two reasons:

  1. The current event has no chance to be processed (i.e. the main thread is processing the previous event, did not complete in time or the looper is blocked for some reason)

  2. The current event is being processed but not completed in time

For example, ANR is generated when a time-consuming operation is performed in onCreate(), resulting in no response from clicks, touches, etc. To avoid ANR exceptions, Android uses the Handler message processing mechanism, which allows time-consuming operations to run in child threads, and sends messages to the UI thread for operations that need to be handled by the UI thread.

We know that the Handler sends messages to the UI thread to process messages, and the UI thread maintains a Looper and a message queue, and the Looper is constantly taking messages from the message queue and distributing them. Here comes the question: wouldn’t the UI thread have to poll the message queue in an endless loop to get messages? Student: Don’t endless loops cause ANR?

Yes, it is, but ANR is another story.

ActivityThread main ()

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); . Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

Looper.loop()

public static void loop() {
    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;
 
    // 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(;;) { Message msg = queue.next(); // might block ... msg.target.dispatchMessage(msg); . msg.recycleUnchecked(); }}Copy the code

Looper.loop() processes messages in an infinite loop. If there is no Looper in the main method, the main thread will exit as soon as it finishes running. ?

So, the main method of ActivityThread is basically a message loop, and once you exit the message loop, your application exits.

So the question is again: why doesn’t this loop cause ANR anomalies?

Looper.loop() constantly receives events and handles them. Every touch or Activity life cycle is controlled by Looper.loop(). If it stops, the application stops. Looper.loop() blocks the message, not looper.loop (), but only a single message or its processing. In other words, the main thread is blocked when the message queue is empty, but the main thread cannot be blocked when the message queue is empty, so the block of 5 seconds is ANR.

In other words, our code is actually in this loop to execute, of course, does not block.

Also, the main thread Looper reads messages from the message queue and blocks when all the messages are read. The child thread sends a message to the message queue and writes to the pipe file, the main thread is woken up, reads from the pipe file, the main thread is woken up just to read the message, and when the message is read, it goes to sleep again. ** Thus loop loops do not have a significant drain on CPU performance.

Conclusion: The looer.loop () method may block the main thread, but as long as its message loop is not blocked, it can always handle events without generating ANR exceptions.

The Handler mechanism has a lot of details to pay attention to: how threads create and exit message loops, etc.)

Here’s how Handler works for you.

So Handler, what thread is new Handler under anywhere?

This needs to be discussed in categories:

  1. Just like we did in the beginning, just new, no Looper parameter, under the thread that created the Looper.
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback) {
        this(callback, false);
    }
    public Handler(boolean async) {
        this(null, async);
    }
    public Handler(Callback callback, boolean async) {
        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;
    }

Copy the code
  1. New the Handler for the specified thread anywhere. For example, the main thread, see the example:
new Thread(new Runnable() {
        @Override
        public void runHandler Handler = new Handler(looper.getMainLooper ()); Handler Handler = new Handler(looper.getMainLooper ()); } }).start();Copy the code

As described above, the UI thread calls prepare differently, and saves the UI thread’s Looper into looper.smainLooper. The goal is to instantiate handlers for the UI thread anywhere.

And the constructor that gets called is

    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

In other words, if I pass another thread’s Looper, I can instantiate another thread’s Handler as well.

Explain the relationship between Message, Handler, Message Queue, and Looper in the single-threaded model

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

  1. The Handler will execute the Message or Runnable to the Message queue.

  2. Looper Each thread has only one Looper. After each thread initializes Looper, the Looper maintains the Message queue of the thread, storing the messages sent by the Handler, and processing the messages sent out of the Message queue. So when we create a Handler for the main thread, it will be bound to a unique Looper for the main thread. Therefore, we use the Handler to send messages to the main thread, and finally to process them asynchronously.

So why, one might ask, do we never need to create a Looper when we use Handler? This is because in the main thread, ActivityThread initializes Looper by default. After preparing, the current thread becomes a Looper thread.

  1. MessageQueue MessageQueue is a MessageQueue used to store messages sent by the Handler. Each thread has at most one MessageQueue. MessageQueue is usually managed by Looper. When the main thread is created, a default Looper object is created. When the Looper object is created, a MessageQueue is created automatically. Other non-main threads do not automatically create Looper.

  2. A Message object is an object stored in a MessageQueue. A MessageQueu can contain multiple messages. When we need to send a Message, we generally do not recommend using the new Message() form. Message.obtain() is more recommended because we define a Message pool in the Message class, and when there are unused messages in the pool, If there are no unused messages, the return is created using new, so obtaining instances using message.obtain () can greatly reduce garbage collection problems when there are a large number of Message objects.

Handler mechanism, Handler other than thread communication

The main uses of this Handler are:

  • Push a Message or Runnable that will be executed at some point in the future to a Message queue.
  • The child thread adds an operation to the message queue that needs to be performed in another thread.

1. Push the Message or Runnable that will be executed at a certain point in the future to the Message queue

Example: A Message or Runnable countdown is implemented by a Handler

  • The first method is to realize the countdown by means of Handler + Message. The code is as follows:
public class MainActivity extends AppCompatActivity { private ActivityMainBinding mBinding; private Handler mHandler ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); / / set to monitor event mBinding. ClickBtn. SetOnClickListener (new View.OnClickListener() {@override public void onClick(View v) {Handler + Messagefor(int i = 1; i <= 10; i++) { Message message = Message.obtain(mHandler); message.what = 10 - i; mHandler.sendMessageDelayed(message, 1000 * i); // Send messages every second by delay}}}); mHandler = newHandler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + ""); // Handle messages in the message queue in handleMessage}}; }}Copy the code

The use of DataBiding may seem strange to those who haven’t used it, but it actually simplifies the code and doesn’t seem to stress students who have a foundation. Handler is used to execute sequential operations at a fixed point in time on the main thread.

  • Method two, through the Handler + Runnable method to achieve the countdown. The code is as follows:
public class MainActivity extends AppCompatActivity { private ActivityMainBinding mBinding; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); / / set to monitor event mBinding. ClickBtn. SetOnClickListener (new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 1; i <= 10; i++) { final int fadedSecond = i; // For each second of delay, send a Runnable object mHandler.postDelayed(new)Runnable() {
                        @Override
                        public void run() {
                            mBinding.time.setText((10 - fadedSecond) + ""); } }, 1000 * i); }}}); }}Copy the code

The Runnable and Message methods are separated because many people don’t understand why the Handler can push Runnable and Message objects.

In fact, whether the Handler adds Runnable or Message to the MessageQueue, it ultimately adds Message to the MessageQueue. This method simply wraps the POST Message and stores the mCallback in the Message that Runnable placed on it. If Message is added directly to MessageQueue, mCallback will be null, so we end up pushing a Message through Handler. Let’s look at the second major use of handlers.

2. The child thread adds an operation that needs to be performed in another thread to the message queue

Example: Use Handler + Message to load images in child threads and display images in UI threads

The renderings are as follows

public class ThreadActivity extends AppCompatActivity implements View.OnClickListener { private ActivityThreadBinding mBinding = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread); / / set the click event mBinding. ClickBtn. SetOnClickListener (this); mBinding.resetBtn.setOnClickListener(this); } @override public void onClick(View v) {switch (v.getid ()) {// Response to load buttoncaseR.iclickbtn: // Start a Thread new Thread(new)Runnable() {
                    @Override
                    public void runFinal bitMap bitmap = loadPicFromInternet(); Handler Handler = new Handler(looper.getMainLooper ()); Handler Handler = new Handler(looper.getMainLooper); Handler. Post (new); // The UI thread's MessageQueue is handled by the Handler's post RunnableRunnable() {
                            @Override
                            public void run() {/ / in the MessageQueue the team when the Runnable operations mBinding. Photo. SetImageBitmap (bitmap); }}); } }).start();break;
            case R.id.resetBtn:
                mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
                break; }} /*** * HttpUrlConnection load image, very simple * @return
     */
    public Bitmap loadPicFromInternet() {
        Bitmap bitmap = null;
        int respondCode = 0;
        InputStream is = null;
        try {
            URL url = new URL("Https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343, 23 & gp 3838991329 & FM = = 0. JPG");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10 * 1000);
            connection.setReadTimeout(5 * 1000);
            connection.connect();
            respondCode = connection.getResponseCode();
            if (respondCode == 200) {
                is = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(is);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "Access failed", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(is ! = null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); }}}returnbitmap; }}Copy the code

The main thing is that when we need to perform some operations on the main thread, we can use this method directly. This method has the inherent advantage of not being able to send messages directly.

Handler mechanism consists of handler mechanism, each part of the source code includes looper loop method, threadLocal concept, dispatchMessage method source, runnable encapsulated message and so on

Remember what I said above?

Explain the relationship between Message, Handler, Message Queue, and Looper in the single-threaded model

  1. Android’s single-threaded model

When a program is started for the first time, Android will start a corresponding Main Thread at the same time. The Main Thread is mainly responsible for processing UI-related events, such as user keystroke events, user touch screen events and screen drawing events, and distributes the related events to the corresponding components for processing. So the main thread is often called the UI thread.

The principles of the single-threaded model must be followed when developing Android applications: Android UI operations are not thread-safe and must be performed in the UI thread.

If you operate directly on the UI thread in a non-UI thread, you will throw an exception, unlike normal Java programs. Because ViewRootImpl validates UI operations, this validation is done by ViewRootImpl’s checkThread method:

    void checkThread() {
        if(mThread ! = Thread.currentThread()) { throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

Due to the UI thread is responsible for monitoring and mapping of events, therefore, must ensure that the UI thread to response to the needs of users at any time, the operation in the UI thread to interruption as short, time-consuming operations (such as network connection) need another thread, otherwise, if there is no response to user requests the UI thread for more than 5 s, will pop up dialog box to alert users to terminate the application. ANR, by default, has a maximum execution time of 5 seconds for an Activity on Android and 10 seconds for a BroadcastReceiver.

If it’s in a new thread why doesn’t the system lock access to UI controls? There are two drawbacks: first, adding locks complicates UI access logic; Second, locking reduces the efficiency of UI access because it blocks the execution of certain threads. Given these two disadvantages, the simplest and most efficient way to handle UI operations is to use a single-threaded model, which is not too cumbersome for developers to use, simply by switching the thread of execution for UI access through handlers. Android uses a complex Message Queue mechanism to ensure communication between threads.

  1. And then there’s the analysis. Message Queue, Handler, Looper

Differences between Handler, Thread, and HandlerThread

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        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(a); } catch (InterruptedException e) { } } }return mLooper;
    }

    /**
     * Quits the handler thread's looper. * 

* Causes the handler thread'

s looper to terminate without processing any * more messages in the message queue. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p><p class="note"> * Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure * that all pending work is completed in an orderly manner. * </p> * * @return True if the looper looper has been asked to quit or false if the * thread had not yet started running. * * @see #quitSafely */ public boolean quit() { Looper looper = getLooper(); if(looper ! = null) { looper.quit();return true; } return false; } /** * Quits the handler thread's looper safely. *

* Causes the handler thread'

s looper to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * Pending delayed messages with due times in the future will not be delivered. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p><p> * If the thread has not been started or has finished (that is if * {@link #getLooper} returns null), then false is returned. * Otherwise the looper is asked to quit and true is returned. * </p> * * @return True if the looper looper has been asked to quit or false if the * thread had not yet started running. */ public boolean quitSafely() { Looper looper = getLooper(); if(looper ! = null) { looper.quitSafely();return true; } return false; } public int getThreadId() { returnmTid; }}Copy the code

Since HandlerThread is a perversely short code, I will post the entire source code directly.

The run() method starts Looper, just like the main thread, so we can use Handler directly. We don’t need looper.prepare () or looper.loop (). Remember that this is not a handler at all, but rather a Thread that encapsulates a Looper, making it easier for child threads to communicate with child threads.

There is also Handler: its message processing is blocking and must be processed one by one. Time-consuming operations should not be handled by handlers.

HandlerThread: Inherits from Thread and has a Looper where time-consuming operations can be performed

What is IdleHandler? What’s the use? How to use

Mp.weixin.qq.com/s/KpeBqIEYe… It hasn’t been written here yet, trying to update…

Handler message mechanism, will postDelayed cause thread blocking? What are the internal effects?

  1. Handler message mechanism
  2. Will postDelayed cause thread blocking?

Remember that? PostDelayed. PostDelayed only adds a delay at the time of post, which will be converted into execution time for each Message. The dead-loop calling next() in loop() is blocking, waking up the thread by writing data to the pipe writer only when the next message arrives or a transaction occurs (the set nextPollTimeoutMillis arrives).

In other words, if all messages in the current Message queue are delayed messages (all messages have not reached the execution time) and this Message is executed earlier than all messages in MessageQueue, The loop retrieving next will be blocked because the earliest Message (postDelayed Message) has not reached execution time. 3. What are the internal effects

Reference article:

Android official documentation: Handler MessageQueue Looper “Android Development Art Exploration” Android Development Step by step to understand the Handler mechanism in Android Android MessageQueue message loop processing mechanism (epoll implementation)