This blog is for me to learn Handler notes, may not be proper writing, in this article I will describe the main source code and part of the classic interview question answers source interpretation. Read time: 20 minutes + This article source version android SDK 28

Update time: 30 December 2019 19:01:22

V1.2: Adding interview questions (message reuse and handler.post processes)

V1.3: Correct my own misunderstanding of MessageQueue and focus on modifying knowledge points corresponding to Message single linked list

preface

As for Google, it is recommended to update the UI in the main thread (in fact, child threads can also update the UI, but not recommended), which can easily make the UI into an unpredictable state. The operation information of UI updating in the worker thread is transferred to the UI main thread, so as to realize the processing of UI updating and drawing by the worker thread, and finally realize the processing of asynchronous message. (Ensure the sequential nature of multi-threaded safety data updates)

Handler processes

Reprint the original picture

The entire handler process is clearly described in the figure above. The important classes involved are:

  • Handler Class
  • MessageQueue class (MessageQueue)
  • The Looper class

A direct relationship between three objects

Handler -> handler.sendMessage ()/post () -> MessageQueue -> messagequeue.next () -> Looper -> looper.prepare () -> Looper.loop() -> handlerMessage() returns to the Handler. One caveat about MessageQueue is that the implementation of this queue is neither a subclass of Collection nor a subclass of Map, but rather Message itself. Because Message is itself a linked list node.

The process of source code

Here mainly tells the source code analysis of the whole process of three objects, along with some interview questions

Handler initialization

public Handler(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()); }}ThreadLocal
      
       .get() ¶
      
        mLooper = Looper.myLooper();	
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        // get the MessageQueue object stored in the Looper object (MessageQueue)
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

MLooper = looper.mylooper () uses threadlocale.get () to retrieve the Loop storage object from a threadlocale.get () thread. Only one Looper can be bound to a Handler. Now that we have a get () method, let’s just look for what’s set in there. Next we go to the Looper source code section.

Which initialization

// quitAllowed MessageQueue Specifies whether to quit
private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    
    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(); }}private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
Copy the code

In prepare (), we pass in a new Looper object for sThreadLocal, and in Looper () we create messageQueue, which retrieves the current thread’s object. So a thread can have multiple handlers, a Handler can only be bound to a Looper, and a MessageQueus in a Looper. By backtracking we can tell the interviewer about Handler Looper MessageQueus’ creation declaration relationship.

PrepareMainLooper () corresponds to a static main () that automatically calls ActivityThread when the main thread is created; PrepareMainLooper () generates a Looper object for the main thread as well as its corresponding MessageQueue object. That’s why we don’t set looper.prepare () on OnCreate anymore.

Handler sends messages

SendMessage ()/postMessage() sends messages, and post() implements a Runable without creating message objects externally.

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
// Jump to sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
 // Jump to enqueueMessage(queue, MSG, uptimeMillis)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

The queue.enqueuemessage () method is visible in the core code, so let’s look at the source code

// enqueueMessage core method

Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
        // If the Message queue is empty, the Message is added to the header
        msg.next = p;
        mMessages = msg;
    } else{... Message prev;for (;;) {
            Prev = p, p.ext
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                 break; }... } msg.next = p;// invariant: p == prev.next
    prev.next = msg;
 }
Copy the code

Message uses next to join Message itself as a node into a single linked list. When represents the time when Message is triggered. Therefore, if when < P. hen is triggered, the loop will exit to insert P.

Looper.loop -> MessageQueue.next

Message loop, which retrieves messages from message queues and distributes them to handlers. Below is part of the core source code

public static void loop(a) {

        // 1. Get the message queue of the current Looper
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            // myLooper() returns the Looper instance stored in sThreadLocal
            final MessageQueue queue = me.mQueue;
            for (;;) {
            
                // Fetch the message from the message queue
                Message msg = queue.next(); 
                if (msg == null) {
                    return;
                }
            // Next () : fetches the message from the message queue
            // If the fetched message is empty, the thread is blocked
            // Sends the message to the corresponding Handler
            msg.target.dispatchMessage(msg);
            // Send the Message Message to the target property of the Message object MSG
            The target attribute is actually a handler objectmsg.recycleUnchecked(); }}Copy the code

Get the MessageQueue via Looper, then fetch the message from the queue, We then call the target in MSG back to our main thread Handler’s handlerMessage () method using the dispatchMessage method, Then call the recycleUnchecked method to set the MSG and next flags to their initial values.

Messagequeue.next ()/dispatchMessage(MSG

Message next(a) {
        // This parameter is used to determine whether there are any messages in the message queue
        // Determines that the message queue should be in the outgoing message/wait state
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

        // In the native layer, if nextPollTimeoutMillis -1, the message queue is in wait state
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
     
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // Dequeue messages, fetch messages from Message queues: in the order in which Message objects were created
            if(msg ! =null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Retrieve the message
                    mBlocked = false;
                    if(prevMsg ! =null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    returnmsg; }}else {
                If there are no messages in the message queue, set the nextPollTimeoutMillis parameter to -1
                // The message queue is in a waiting state the next time the loop loops
                nextPollTimeoutMillis = -1; }... }... }}Copy the code

The main functions of the above code are commented, through looper. loop to msgQueue.next loop queue, get useful messages to exit the queue, update the identity without messages, block the loop. The above MSG. Target. DispatchMessage (MSG); So the target is actually the Handler object and we traced it back to the dispatchMessage source

Handler Message processing source code

public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}public interface Callback {
        / * * *@param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
    
    // The subclass method we must implement
    public void handleMessage(Message msg) {}Copy the code

When dispatchMessage(MSG) is sent, it determines the mode of sending once: If msg.callback is not null, the message was sent using POST (Runnable r). If msG. callback is null, the message was sent using POST (Runnable r). SendMessage (MSG) is used to send the Message, then callback the overwritten handleMessage(MSG)

The interview questions

Message creates the recycle linked list relationship

Share the ins and outs of Message Obtain () and Recycle (), a feel-good blog with images and source code

MessageQueue Creation time

MessageQueue was created with Looper

The role of ThreadLocal in the Handler mechanism

SThreadLocal is a ThreadLocal object that can store variables in a thread. SThreadLocal stores a Looper object from looper.prepare (). There is one handler for every Looper.

Stars, the Handler, MessageQueue references?

Here is a direct quote from above: Handler -> handler.sendMessage ()/post () -> MessageQueue -> messagequeue.next () -> Looper -> looper.prepare () -> Looper.loop() -> handlerMessage() returns to Handler “.

Handler Post is different from sendMessage

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
Copy the code

The post method assigns values to MSG callback and target. The Handler. DispatchMessage method has a separate feedback process for the two methods.

Handler causes memory leak problem?

This problem is less stable after three years of work experience, mainly examining the life cycle and GC problems. Because the anonymous inner class Handler contains an external object, the Activity object, it can cause a memory leak if the interface is destroyed but the Queue behind it remains unprocessed.

Simple solution:

static class MyHandler extends Handler {
	// Weak reference Avtivity
	WeakReference mActivityReference;
	// If the interface destroys the handler, let the GC clean it up
	MyHandler(Activity activity) {
		mActivityReference= new WeakReference(activity);
	}

	@Override
	public void handleMessage(Message msg) {
		final Activity activity = mActivityReference.get(a);if(activity ! =null) { mImageView.setImageBitmap(mBitmap); }}}Copy the code

Have you seen the Handler synchronization barrier mechanism?

To be honest, this is a bit unusual, but it can be seen whether you have a deep interpretation of the source code. (I also heard it from my friend Jengo. 360 boss is awesome.

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

The async Boolean value of the code above identifies synchronization and asynchrony

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

Call msg.setasynchronous (true) when adding Message

Synchronization barriers are used to prioritize messages in MessageQueue. By turning on the synchronization barrier, asynchronous messages can be processed first and returned during looper loops. Let’s look at the source code:

private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when! =0) {
                while(p ! =null && p.when< =when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            returntoken; }}Copy the code

Looper.loop () : null for Message

Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        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) {
                    // 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) {
                        // 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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                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);
            }

            // 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 we do 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; }}Copy the code

From the above code, we know that when MessageQueue fetches a Message whose target is NULL, the asynchronous Message is executed first, and the added Message is processed first through a mechanism called synchronization barrier to modify the list order.

Why doesn’t the main thread Looper. Loop freeze

Since I didn’t really understand this problem at the time, this problem covered a little bit more, not only to say Handler but also to say Linux epoll mechanism. In order to keep the App running, we do not want to be run for a period of time, we will not quit, so how to ensure that the survival of the App? Binder threads, for example, also use an infinite loop to write and write to binder drivers in a different way. Of course, they do not simply loop forever, falling asleep when there is no message. If the main thread MessageQueue has no message, it blocks the nativePollOnce() method in the loop queue.next(). See Android message mechanism 1-Handler(Java layer), where the main thread releases CPU resources and goes to sleep until the next message arrives or a transaction occurs, waking up the main thread by writing data to the pipe end. The epoll mechanism adopted here is an IO multiplexing mechanism, which can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), it immediately notifies the corresponding program to carry out read or write operations, which is essentially synchronous I/O, that is, read and write is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources. For more details about Git Yuan, see Git Yuan for more details.

Message reuse mechanism

In Loop. Looper we can explore why.

/ / Loop

for (;;) {
            Message msg = queue.next(); // might block.// This must be in a local variable, in case a UI event sets the logger
            finalPrinter logging = me.mLogging; .try {
                // Message distribution target refers to Handler (this is also the difference between Handler sendMsg and POST)
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if(traceTag ! =0) { Trace.traceEnd(traceTag); }}...final long newIdent = Binder.clearCallingIdentity();
            We'll focus on this method
            msg.recycleUnchecked();
        }


/ / the Message class

void recycleUnchecked(a) {
        // Change the tag to join the message pool and reset all states
        flags = FLAG_IN_USE;
        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) {
                // Set the head node to Next to sPool, the latest head node for the current object
                next = sPool;
                sPool = this; sPoolSize++; }}}Copy the code

Let’s also look at what’s going on with message.obtain ()


// Part of the object to annotate
 public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

// Message reuse is the main method
public static Message obtain(a) {
        synchronized (sPoolSync) {
            // Check whether the head node is null
            if(sPool ! =null) {  
                // Fetch the header and set the next message as the header
                Message m = sPool;  
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                returnm; }}// Return a new MSG if the list is null
        return new Message();
 }

Copy the code

Message uses static single-linked lists to reuse messages globally, and Message data is reset during each collection to prevent Message from holding other objects and causing memory leaks. Use Mesaage.

MessageQueue. RemoveMessages in operation

This part of the fish also friends always ask me, don’t answer made a baidu, and attach the reference articles address: MessageQueue. RemoveMessages in operation

Handler.post

The difference between handler. sendmsg and handler. post is that the post method only needs to implement the runable method. What does it do underneath?

// handler.class
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }


private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        // Assign the MSG callback
        m.callback = r;
        return m;
    }



// The MSG callback is classified in the message distribution code in loop. Loop.

public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {

            // Callback the runable method in the POST method
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; }}// Go back to the handleMessage method of handlerhandleMessage(msg); }}Copy the code

Part of the explanation is in the comments of the code. Handler himself learning and part of the summary of the interview questions about these. Subsequent maintenance and updates will continue.