Basic concept

  • Handler Indicates the sender and receiver of a message
  • Message Indicates the carrier of the Message
  • The MessageQueue message list is inserted according to the time when
  • Looper Pump station extracts messages from MessageQueue

Handler

Read the source code with questions

  • Message Indicates the priority of receiving messages
  • How are asynchronous messages created
  • Priority of receiving messages

A constructor

So let’s look at the constructors, there are ultimately 2 constructors, so we can say one that passes in Looper and one that doesn’t and the difference between the two is whether you want to get the Looper yourself. (Can’t this be done by reloading?)

  • A thread has only one Looper and one MessageQueue, but can have multiple handles.
  • Message finds the specified Hander to call back to via target

The first:


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

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


Copy the code
/ * * *@paramThe key to looper thread switching is that there is only one in a thread and it can only be initialized once *@paramCallback Message, which can only be passed in * from the constructor@paramAsync Is an asynchronous message. Asynchronous messages have higher priority in queues */
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

Send a message

SendMessageAtTime, SystemClock#uptimeMillis, is the time after startup and is also used to insert the queue

The first kind of

/ * * *@paramThe Runnable of the r callback ultimately specifies message.callback *@paramToken that is used to cancel using removeCallbacksAndMessages *@paramUptimeMillis time How long after startup postAtTime(@NonNull Runnable r, @Nullable Object token, long uptimeMillis)
Copy the code

The second,

/ * * *@paramMSG Message to be sent *@paramUptimeMillis How long after startup is used to queue sendMessageAtTime(@NonNull Message msg, long uptimeMillis)
Copy the code

The third type of message is placed at the head of the queue, which is where uptimeMillis is set to 0

postAtFrontOfQueue(Runnable r)
sendMessageAtFrontOfQueue(Message msg)
Copy the code

Receives the message

Priority:

  • Callback (post (Runnable r))
  • The mCallback passed in the mCallback constructor
  • Handler method handleMessage
public void dispatchMessage(@NonNull Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code

The team

There is only one key point here, which is the oft-stated reason why handlers cause memory leaks. This lets Message hold the Handler object. But messages are thrown to queues. This can cause a memory leak if the Message is still in the queue after the Activity exits. Cause: When the system GC, Message refers to Handler because Handler refers to an Activity. The system will not recycle the Activity

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

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Message

A singly linked list pattern that uses next to fetch the next Message. MessageQueue is not a MessageQueue it’s a tool that handles message queues and in this case it’s mostly entity classes and the only thing you can say is cache application write cache

void recycleUnchecked(a) {
    // Clear out all other details.. .synchronized (sPoolSync) {
        // If the maximum value of MAX_POOL_SIZE is not reached, the value is 50
        if (sPoolSize < MAX_POOL_SIZE) {
            // sPool is a Message object
            // This puts the current message at the head of the list
            
            // The first operation puts the current sPools into the next node
            next = sPool;
            // The second operation points the first node to the head
            sPool = this; sPoolSize++; }}}Copy the code

Getting cached messages

public static Message obtain(a) {
    synchronized (sPoolSync) {
        
        if(sPool ! =null) {
            // Step 1: Remove the header message
            Message m = sPool;
            // Step 2: the cache points to the second node
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            returnm; }}// If there is no cache, use Message directly
    return new Message();
}
Copy the code

MessageQueue

MessageQueue is not actually a MessageQueue. It’s just a class that handles a list of messages. There are two main classes in this list. Take a message

 

The team

boolean enqueueMessage(Message msg, long when) {
    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) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // The first case is directly joined when the queue head joins the queue head
        // 1. p == null
        / / 2. The when = = 0 to join the queue first, said in the Handler postAtFrontOfQueue (Runnable r) sendMessageAtFrontOfQueue (Message MSG) when two ways this is 0
        // 3. The current message When is smaller than the current queue leader When
        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;
            
            // Insert the list through the for loop
            // Confirm the location of the two points
            // 1. If there is no child node, the queue is inserted directly at the end of the queue
            // 2. The current time is smaller than the time of the next node
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false; }}// Pull the two nodes up hand-in-hand
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr ! = 0 because mQuitting is false.
        // mBlocked is called to see if the wake-thread wait is needed, where an assignment is made when a message is fetched
        if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code

To get the message

Message next(a) {... .for (;;) {
        if(nextPollTimeoutMillis ! =0) {
            Binder.flushPendingCommands();
        }
        If nextPollTimeoutMillis == -1, wait for milliseconds if nextPollTimeoutMillis > 0
        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 target is null, it is a message barrier.
            // This is where emergency messages are processed first
            If the first message is a barrier message, the asynchronous message is fetched from the queue for processing
            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 there is a current message, the message is processed
            if(msg ! =null) {
                // Wait if the current first message is later than the current time
                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 {
                    // Blocked is not needed when a message is retrieved
                    mBlocked = false;
                    // prevMsg ! = null, when will this appear again?
                    // prevMsg is not null only if the barrier message is fetched
                    if(prevMsg ! =null) {
                        // Put the prevMsg back in the list
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    // The message needs to be processed, so break the link list
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    returnmsg; }}else {
                // If there is no message, it will wait forever
                nextPollTimeoutMillis = -1;
            }

            // If you are already exiting after the wait, no further processing is required
            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 there is no IdleHandler then the thread needs to wait
            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);
        }

        // IdleHandler callback, and whether to keep, if not remove
        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);
                }
            }
        }

    }
}
Copy the code

Looper

Looper is the pump station for Handler message processing. Its main function is to continuously fetch messages from MessageQueue and hand them to a Handler to process messages. Looper uses ThreadLocal for thread switching.

The initial

public static void prepare(a) {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if(sThreadLocal.get() ! =null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // create a Looper object and put it into ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code

Start cycle

public static void loop(a) {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    finalMessageQueue queue = me.mQueue; . .for (;;) {
        // Get Message via MessageQueue's next() method
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }... .try {
            // Drop the message to handler. dispatchMessage to process the message
            msg.target.dispatchMessage(msg);
            if(observer ! =null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            ......
            throw exception;
        } finally{... }... .// The message is recycled and merged into the cachemsg.recycleUnchecked(); }}Copy the code