An overview of the

Handler is mainly used for communication between threads. Handler is mainly composed of MessageQueue, Message, Looper and Handler. It is called Handler Message mechanism

  • HandlerMainly responsible for sending messages and processing messages
  • MessageQueueMainly responsible for storing messages
  • LooperMainly responsible forMessageQueueTo retrieve the message and distribute it toHandler
  • ThreadLocalIs mainly responsible for storing different threadsLooperobject
  • MessageMainly responsible for data storage

ThreadLocal

ThreadLocal is an internal data store class that can store data in a specified thread. Once the data is stored, only the specified thread can retrieve the stored data. Other threads cannot retrieve data. ThreadLocal is generally considered when some data is thread-scoped and different threads have different copies, such as handlers who need to fetch loppers from different threads. In this case, ThreadLocal can easily store loopers from different threads

Another use of ThreadLocal is for object passing of complex logic, such as listener passing. Sometimes a thread’s task is too complex, which can be represented by a deep stack of functions and a variety of code entries. In such cases, we need listeners to run through the whole thread. Let the listener exist as a global object of the thread, and the listener can be obtained within the thread simply by getting

The use of ThreadLocal

    mThreadLocal = new ThreadLocal<>();
        mThreadLocal.set(true);
        Log.d("mmm"."Current thread"+Thread.currentThread()+"ThreadLocal to store"+ mThreadLocal.get());
        new Thread("thread1") {@Override
            public void run(a) {
                super.run();
                mThreadLocal.set(false);
                Log.d("mmm"."Current thread"+Thread.currentThread()+"ThreadLocal to store"+ mThreadLocal.get());
            }
        }.start();
        new Thread("thread2") {@Override
            public void run(a) {
                super.run();
                Log.d("mmm"."Current thread"+Thread.currentThread()+"ThreadLocal to store"+ mThreadLocal.get());
            }
        }.start();
Copy the code

Thread1 is set to false. Thread2 is not set. As usual, thread1 is set to true, thread1 is set to false, thraed2 is null

09-28 11:30:12.616 32536-32536/com.example.jh.rxhapp D/ MMM: Thread[main,5, the main] ThreadLocal to storetrue
09-28 11:30:12.618 32536-32745/com.example.jh.rxhapp D/ MMM: Thread[thread2,5, the main] ThreadLocal to storenull
09-28 11:30:12.619 32536-32744/com.example.jh.rxhapp D/ MMM: Thread[thread1,5, the main] ThreadLocal to storefalse
Copy the code

ThreadLocal source

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
    }
Copy the code

If ThreadLocalMap is null, create a ThreadLocalMap with the current thread and store the data. Let’s see how ThreadLocalMap is created

   void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    
    class Thread implements Runnable {... ThreadLocal.ThreadLocalMap threadLocals =null;
    }
Copy the code

Each Thread has a ThreadLocalMap object inside it. If this object is null, it is reassigned

  private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for(Entry e = tab[i]; e ! =null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

First compute the array subscript with key, then fetch the value from Entry[], reassign if there is data, or create a new Entry to add to the Entry[] array if there is no data

Now let’s look at the get method

   public T get(a) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}return setInitialValue();
    }
    
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    
    private Entry getEntry(ThreadLocal
        key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if(e ! =null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
    
    
Copy the code

First, the ThreadLocalMap of this thread is obtained. If it is not Null, the subscript of the Entry[] array is computed by key. Then the Entry is retrieved, and the specific value is retrieved

  private T setInitialValue(a) {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
Copy the code

ThreadLocal summary

Every thread has a ThreadLocal ThreadLocalMap threadLocals = null; When we operate on a set of get methods in ThreadLocal, we operate on a ThreadLocalMap object in a single thread. ThreadLocalMap stores data in an Entry[] array, so each thread has a different value

Lopper

Create Lopper

 public static void prepare(a) {
        prepare(true);
    }
    
 private static void prepare(boolean quitAllowed) {
        // Only one looper can be created per thread
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
Copy the code

Prepare (true) is called by default to allow Looper to exit. False is called to prevent Looper from exiting. This implements a Looper per thread and creates a MessageQueue when creating a Looper

prepareMainLooper

This method is mainly used in ActiityThread only, creating the main thread Looper

   public static void prepareMainLooper(a) {
        // This Looper is not allowed to exit
        prepare(false);
        synchronized (Looper.class) {
            // Set this Looper to the main thread Looper only once
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}// Get the main thread Looper
 public static Looper getMainLooper(a) {
        synchronized (Looper.class) {
            returnsMainLooper; }}Copy the code

loop()

    public static void loop(a) {
        // Get the thread's looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        / / get MessageQueue
        finalMessageQueue queue = me.mQueue; .for (;;) {
            // Retrieve the message from MessageQueue, no message will be blocked
            Message msg = queue.next(); // might block
            if (msg == null) {
                MSG will return null only when messageQueue exits
                return; }...// MSG. Target is the Handler object that sends messages to the Handlermsg.target.dispatchMessage(msg); .// Add Message to the Message poolmsg.recycleUnchecked(); }}Copy the code

The loop() method enters an infinite loop, repeating the following

  • fromMessageQueueRemove theMessage
  • theMessageDistribute to the correspondingHandler
  • After the distributionMessageRecycle to the message pool for reuse

quit()

  public void quit(a) {
        // Remove the message
        mQueue.quit(false);
    }

  public void quitSafely(a) {
        // Safely remove the message
        mQueue.quit(true);
    }
Copy the code

Quit () calls MessageQueue’s quit method. The difference between the two methods is that the quit method directly quitSafely exits after the rest of the message is executed

Which summarizes

The main job of Looper is to get the message from MessageQueue and distribute it to the corresponding Handler. All threads except the main thread need to create Looper by calling looper.prepare (). Looper for the main thread is created in ActivityThread’s main method, and Looper. Loop is called after the Looper is created. Here is a classic example of Looper creation

 class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run(a) {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here}}; Looper.loop(); }}Copy the code

Handler

A constructor

  public Handler(a) {
        this(null.false);
    }
    
  public Handler(Callback callback) {
        this(callback, false);
    }
    
 public Handler(boolean async) {
        this(null, async);
    }
    
  public Handler(Callback callback, boolean async) {...// Get looper in this thread
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        // Get MessageQueue from looper
        mQueue = mLooper.mQueue;
        // Whether Callback is set
        mCallback = callback;
        // Whether it is asynchronous
        mAsynchronous = async;
    }
Copy the code

Each constructor ends up calling a two-argument constructor. For a constructor with no arguments, the default is looper, callback null, and message synchronized

  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

A constructor that takes Looper and can specify Looper

Send a message

This is sending a message invocation chain, we found that the final is invoked the MessageQueue. EnqueueMessage ()

send

  public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

  public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

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

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

post

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

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

Handler. SendEmptyMessage () series methods, eventually invokes the MessageQueue. EnqueueMessage (MSG, uptimeMillis), add a message to the message queue, which uptimeMillis is the system time and delay time

Distribution of the message

In the stars. The loop () method, found a message, will call MSG. Target. DispatchMessage method, to distribute the message

   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

Distribution process

  • whenmsgThere arecallbackIs calledmessage.callback.run(); Method, among themcallbackRefers to theRunnable
  • ifcallbackNull, then look at the value of the member variablemCallbackIs it empty? This is zeroHandlerThe constructor is passed in
  • ifmCallbackIs also empty, then callshandleMessageMethod, this is usually inHandlerOverride in a subclass of

Other methods

removeMessages

Remove the message, which is actually the MessageQueue of the operation

  public final void removeMessages(int what, Object object) {
        mQueue.removeMessages(this, what, object);
    }
Copy the code

Handler to summarize

The Handler sends the message and eventually inserts the message into the MessageQueue. Looper. Loop takes the message out of the MessageQueue and sends it out to the Handler, completing the loop

MessageQueue

MessageQueue is the link between Java layer and c++ layer. Most of the core methods are entrusted to the native layer. The native methods in MessageQueue are as follows

    private native static long nativeInit(a);
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
Copy the code

To learn more about what these native methods do, go to Gityuan’s blog on Android Messaging 2-Handler(Native Layer).

Create a MessageQueue

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // Initialize the message queue with native code
        mPtr = nativeInit();
    }
Copy the code

EnqueueMessage inserts the message

   boolean enqueueMessage(Message msg, long when) {
        // MSG. Target cannot be empty
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {... msg.markInUse(); msg.when = when; Message p = mMessages;boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // If p==null indicates that the message queue is empty, or the MSG message is triggered at the earliest time in the queue, the message is inserted into the header, and the queue is woken up if blocked
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Insert into the queue in chronological order, no need to wake up 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;
            }

            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

The insertion of MessageQueue, which is actually the insertion of linked list, is arranged according to the sequence of triggering time of Message. The Message header is the first to be triggered. When there is a Message queue, it will iterate from the beginning until it finds the appropriate position of the Message to be inserted, so as to ensure the chronological order of all messages

Next gets the message

  Message next(a) {
        final long ptr = mPtr;
        Return null if the message loop has exited
        if (ptr == 0) {
            return null;
        }
        // Note that the first loop here is -1
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
            // Block the operation, wait for nextPollTimeoutMillis, or wake up
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
            
                Message prevMsg = null;
                Message msg = mMessages;
                // Synchronization barrier
                if(msg ! =null && msg.target == null) {// Set a synchronization barrier
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                // The do while loop iterates through the list of messages
                When out of the loop, MSG points to the asynchronous message nearest the table header
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null && !msg.isAsynchronous());
                }
            
                if(msg ! =null) {
                    if (now < msg.when) {
                       // Reset the blocking time if the current time is less than the next test trigger time
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // If the message queue is not empty and the current time is greater than or equal to the time when the message was triggered, the message is returned directly and removed from the message queue
                        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 {
                    // Set nextPollTimeoutMillis to -1 if there is no message
                    nextPollTimeoutMillis = -1;
                }

                // Returns null if the message is being pushed out
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // This is idleHandler, notice pendingIdleHandlerCount is less than 0, it doesn't enter if it's equal to 0, and when it's less than 0, it's the first time it enters the loop, and it's assigned -1
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                PendingIdleHandlerCount <= 0, continue if less than or equal to 0
                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.
            Run IdleHandler, but only in 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.
            // Reassign it to 0, which means that IdleHandler only executes once
            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
  • Enter first to check whether you have exited, exit directly return, do not exit to proceed to the next step

  • Then determine whether the current MessageQueue is empty. If it is empty, the blocking time nextPollTimeoutMillis = -1.

  • NextPollTimeoutMillis = (int) math.min (msg.when – now, integer.max_value); nextPollTimeoutMillis = (int) math.min (msg.when – now, integer.max_value);

  • If the current time is greater than or equal to the trigger time, the message is returned and removed from the queue

  • One method is nativePollOnce(PTR, nextPollTimeoutMillis); This is a native method that blocks, and nextPollTimeoutMillis represents the blocking time

    • Among themnextPollTimeoutMillis=-1Blocks until woken up
    • Among themnextPollTimeoutMillis=0Does not block and returns immediately
    • Among themnextPollTimeoutMillis>0It means blockingnextPollTimeoutMillisMilliseconds, also returned immediately if awakened during

Synchronization barrier

Also involves a knowledge point above, synchronous barrier, we can through the MessageQueue. PostSyncBarrier methods to set

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

So the method is to create a Message and put it on the Message queue, and there’s nothing special about it, but there’s a special point here where Message doesn’t assign a value to Tagret, right

We typically send messages that call handler. sendMessage and assign message. Tagret internally

//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    / /...
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

The role of synchronization barriers

The code above is commented, and when a synchronization barrier is encountered, a do while loop is executed with the condition MSG! = null && ! MSG. IsAsynchronous (), which means to skip synchronous messages and return asynchronous messages

How do I send asynchronous messages

Usually we send synchronous messages. If we send asynchronous messages at will, we simply pass async=true in the Handler constructor

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);
Copy the code

IdleHandler

The above source code continues to analyze is IdleHandler, I wrote a previous article Android LeakCanary use and principle,LeakCanary uses IdleHandler

 void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle(a) {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false; }}); }Copy the code

The IdleHandler does something we want to do when the current thread message queue is idle, but the IdleHandler only does it once, as described in the comments above

Message

Message mainly contains the following information

The data type Member variables explain
int what News category
long when Message trigger time
int arg1 Parameter 1
int arg2 Parameter 2
Object obj The message content
Handler target Message responder
Runnable callback The callback method

The message pool

Message maintains a Message pool. The Recycle () method dumps used messages into the Message pool. The advantage of this method is that when the Message pool is not empty, messages can be retrieved from the pool instead of being recreated, which improves efficiency

The static variable sPool has a data type of Message, which is actually a linked list. It maintains the Message pool, and MAX_POOL_SIZE represents the capacity. The default value is 50

recycle()

    public void recycle(a) {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
    
   void recycleUnchecked(a) {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this; sPoolSize++; }}}Copy the code

It’s just an insert of a linked list, clear the information, and insert

Obtain () obtain messages from the message pool

 public static Message obtain(a) {
        synchronized (sPoolSync) {
            if(sPool ! =null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                returnm; }}return new Message();
    }
Copy the code

If sPool is not null, a Message is fetched from the pool. If sPool is null, a New Message is returned

Why doesn’t the main thread in Android get stuck because of the dead loop in looper.loop ()

To thoroughly understand the problem completely, the need to prepare the following four aspects: the Process/Thread, Android Binder IPC, Handler/stars/MessageQueue message mechanism, Linux pipe/epoll mechanism

Why doesn’t the main thread in Android get stuck because of the dead loop in looper.loop ()?

conclusion

  • The Handler sends the message via the sendMessage method and inserts it into MessageQueue
  • Looper loops through the Message and distributes it to the Handler
  • Then send the dispatchMessage to the corresponding method for processing

Reference: gityuan.com/2015/12/26/…

Android development art exploration