preface

This article intends to talk about the Handler principle, I believe that the Handler principle has been written by a lot of bigwigs, but I want to write from a different Angle, from the practice to understand the handler principle. I hope this article brings you some results.

Handler is introduced

Handler is a set of Android messaging mechanisms, such as AsyncTask, IntentService, activity lifecycle control, etc. There are four important objects in the handler mechanism:

  • Handler is responsible for sending and processing messages
  • MessageQueue MessageQueue (although its name sounds like a queue, it is actually a one-way linked list)
  • Message Indicates the transmitted Message
  • Looper polls the message and sends the message to the handler that holds it (only one Looper per thread)

1. Implement handler

First let’s look at the constructor of handler source code

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


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

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

        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

The callback argument calls handlerMessag and we will receive the Message Looper argument in the thread where the handler resides: MQueue is a Messagequeue. Messagequeue is a member variable of looper. As you can see, an error will be reported if there is no Looper. Async: indicates whether an asynchronous message is sent

Here we emulate source code to implement a simple handler class

public class Handler { private final MessageQueue mQueue; private Looper mLooper; public Handler(){ Looper looper=Looper.myLooper(); mLooper=looper; mQueue=looper.mQueue; Public void sendMessage(Message Message){message.target=this; mQueue.enqueueMessage(message); Public void handleMessage(Message Message) {}}Copy the code

2. Implement Looper

We use looper.mylooper () as an entry point to look at the source of Looper

/** 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() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } /** * 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() { return sThreadLocal.get(); }Copy the code

Prepare (Boolean quitAllowed) is allowed to create only one looper on a thread. Prepare (Boolean quitAllowed) is allowed to create only one looper on a thread. The ActivityThread was automatically created for me on the main thread, so any other thread that needs Looper needs to manually call looper.prepare ().

How does Looper act as a message pump and pass messages to handlers? Let’s look at the source of looper. loop (with clipping)

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the 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; . . for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } try { msg.target.dispatchMessage(msg); . }...Copy the code

Can see which Message polling is an infinite loop, and constantly invoke Message MSG = queue. The next () from the MessageQueue Message, and then call MSG. Target. DispatchMessage (MSG); The target in this case is the handler, so we’re passing the message to the handler;

Here we implement a simple Looper that mimics source code

Public class Looper {// For Looper stored in the thread, to ensure that a thread has only one Looper, // When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each thread that uses the variable, Private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>(); MessageQueue mQueue; private Looper(){ mQueue =new MessageQueue(); } public static Looper myLooper(){ return sThreadLocal.get(); } public static void prepare(){ if (null! =sThreadLocal.get()){ throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } public static void loop(){ Looper looper=myLooper(); if (looper==null){ throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } MessageQueue queue=looper.mQueue; for (;;) { Message next=queue.next(); if (next==null||next.target==null){ continue; } next.target.handleMessage(next); } } public void quit(){ mQueue.quit(); }}Copy the code

3. Implement MessageQueue

I will use the above queue.nxet() as a starting point to enter the source code to take a look

Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. 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(); return msg; } } else { // 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); } // 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

NativePollOnce (PTR, nextPollTimeoutMillis); Execute Native’s message mechanism. The method waits for Native messages, where timeOutMillis is the timeout waiting time. A value of -1 indicates an infinite wait until an event occurs. If the value is 0, there is no need to wait for an immediate return, so the main thread polling all the time does not consume CPU performance, nor does it cause a lag because it is woken up as soon as a message arrives. If you want to learn more about the native messaging mechanism, you can look at Java Binder and MessageQueue. Next, you can look at if (MSG! = null && msg.target == null) if there is a barrier message, then it will filter out the synchronous message and execute the nearest asynchronous message directly. Some idle tasks, such as GC, are executed, and nextPollTimeoutMillis is reset to 0 after the idle tasks are executed.

The MessageQueue is how to save the message, when the Handler sends the message, whether it’s called sendMessage, sendEmptyMessage, sendMessageDelayed or other sends a series of methods. SendMessageAtTime (Message MSG, long uptimeMillis) is eventually called, whereas this method ends up calling enqueueMessage(Message MSG, Long When).

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
Copy the code
boolean enqueueMessage(Message msg, long when) { ... Omitting synchronized (this) {... Omit the MSG. MarkInUse (); msg.when = when; Message p = mMessages; boolean needWake; 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 { // 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(); 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); } } return true; }Copy the code

If there is no message in the message queue or the waiting time is less than the waiting time of the message header, the message will be directly placed in the header. Otherwise, the message will be inserted into the appropriate position (compare the waiting time). If needWake is triggered, the native message will be directly woken up. Here the MPTR is the address that the NativeMessageQueue object then returns.

Here we emulate the simple MessageQueue implementation

Public class MessageQueue {// Message mMessage; private boolean isQuit; public void enqueueMessage(Message message){ synchronized (this){ if (isQuit){ return; } Message p=mMessage; if (null==p){ mMessage=message; }else { Message prev; for (;;) { prev =p; p=p.next; if (null==p){ break; } } prev.next=message; } notify(); // Wake up, simulate nativeWake(mPtr); } } public Message next(){ synchronized (this){ Message message; for (;;) { if (isQuit){ return null; } message=mMessage; if (null! =message){ break; }try { wait(); // Wait to wake up to simulate nativePollOnce(PTR, nextPollTimeoutMillis); }catch (InterruptedException e ){ e.printStackTrace(); } } mMessage=mMessage.next; return message; } } public void quit(){ synchronized (this){ isQuit=true; Message message=this.mMessage; while (null! =message){ Message next=message.next; message.recycle(); message=next; } notify(); }}}Copy the code

4. Implement Message

Message

/*package*/ Handler target; /*package*/ Runnable callback; // sometimes we store linked lists of these things /*package*/ Message next; /** * 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; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }Copy the code

The target is the handler with a message. So you can target. HandlMessage () to let the handler to handle the news, there is a next, so that he can produce a message in the messageQueue singly linked list. There is also a variable pool for those interested to learn more about message reuse

Here we emulate the simple Message implementation

public class Message { int what; Object object; Message next; Handler target; public void recycle(){ object=null; next=null; target=null; }}Copy the code

Now that we’ve analyzed and simulated the handler mechanism, let’s take a look.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Looper.prepare(); Handler =new Handler(){@override public void handleMessage(Message Message){// Handle the Message Log.i("info1", "handleMessage: "+message.what); }}; new Thread() { @Override public void run() { Message message=new Message(); message.what=45; handler.sendMessage(message); }}.start(); Looper.loop(); // enable polling}Copy the code
The 2020-01-02 16:40:38. 862, 22050-22050 /? I/info1: handleMessage: 45Copy the code