Handler message mechanism, explaining the relationship between Handler, Message, MessageQueue, Looper

Keywords: Android less Attribute

What is handler?

Handler is a communication mechanism between threads and processes.

Relationships among Handler, Looper, MessageQueen, Message

Message: Message object
MessageQueen: Queue for storing message objects
Looper: Looper is responsible for reading messages in MessageQueen in a loop and handing them to Handler for processing after reading the messages.
Handler: Send and process messages

Handler Source Parsing

To use handler, you first need to ensure that the current thread has a Looper object

 Looper.prepare();
 ...
 Looper.loop()

Class Looper

Looper Construction Method

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

When a Looper object is created, MessageQueue is created and Looper is bound to the current thread. But instead of calling the constructor directly to get the Looper object, we use Looper's prepare() method.
prepare() uses ThreadLocal to save the current Looper object. The ThreadLocal class can isolate the data threads to ensure that only the current thread's Looper object can be obtained in the current thread. At the same time, prepare() ensures that the current thread has and only one Looper object, indirectly guarantees that a thread has only one MessageQueue object.

prepare() of Looper

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

Looper opens 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;
   Binder.clearCallingIdentity();
   final long ident = Binder.clearCallingIdentity();
   for (;;) {
       Message msg = queue.next(); // May block may be blocked and parsed in next method later
       if (msg == null) {
           return;
       }
       try {
           msg.target.dispatchMessage(msg);
       } finally {
           if (traceTag != 0) {
               Trace.traceEnd(traceTag);
           }
       }
       msg.recycleUnchecked();
   }
}

Lopper opens an infinite loop through loop(), and obtains message objects through next() of MessageQueue. Once acquired, call msg.target. dispatch MEssage (msg) to hand the msg to the handler object (msg.target is the handler object), and finally recycle it.

Class Handler

Hanlder instantiation

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

Get the MessageQueue object of the current thread in the instantiation process so that messages can be added to MessageQueue

send message

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

Queuing messages

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

In enqueueMessage, msg.target is first assigned to this, which lays the groundwork for sending out the message queue to handler for processing.

Processing message

 public void dispatchMessage(Message msg) {
     if (msg.callback != null) {
         handleCallback(msg);
     } else {
         if (mCallback != null) {
             if (mCallback.handleMessage(msg)) {
                 return;
             }
         }
         handleMessage(msg);
     }
  }

We mentioned earlier that when Looper.loop() gets a message, it calls handler's dispatchMessage method to process the message. Handler processes the message by calling our rewritten handleMessage() method, or we can implement the Callback interface when we create the handler instance, which can handle the message from MessageQueue as well.

MessageQueue class

MessageQueue Construction Method

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

The MessageQueue initialization process initializes the underlying NativeMessageQueue object and holds the memory address (long) of the NativeMessageQueue.

next() of MessageQueue

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(); //Brush it as a performance optimization operation for Android systems
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);//The underlying implementation of native blocking, blocking status can be awakened by new messages, the first entry will not be delayed
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;//Get header node messages
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);//Obtain clogging time
                } else {
                    mBlocked = false;
                    if (prevMsg != null) { //The header node points to the second message object in the queue
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;   //Return to looper directly out of the queue
                }
            } else {
                nextPollTimeoutMillis = -1;//The queue has no news and has been blocked
            }
            if (mQuitting) {
                dispose();
                return null;
            }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

Although looper also opens the loop, it calls next() of MessageQueue when it really works. To figure out what blockage is, first look at the three corresponding conditions.
NextPoll Timeout Millis = 0 is not blocked
NextPoll Timeout Millis < 0 has been blocked
NextPoll Timeout Millis > 0 blockage corresponds to a length of time and can be awakened by new messages
In next(), since message queues are sorted by delay time, the header message, which has the smallest latency, is considered first. When the header message is empty, it means there is no message in the queue. NextPoll Timeout MIllis is assigned a value of - 1. When the header message delay time is longer than the current time, the difference between the delay time and the current time is needed for blocking the message.
When the message delay time is less than or equal to 0, return msg directly to handler for processing
Native PollOnce (ptr, nextPoll Timeout Millis) is a native underlying implementation of blocking logic. The blocking state can be changed by time or waked up by new messages. Once waked up, the header message will be retrieved, and the blocking or returning message will be re-evaluated.

enqueueMessage()

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

When a message is placed on the stack, it is first judged that if the new message is the first message or if the new message has no delay or if the delay time of the new message is less than that of the first message in the queue, the message will be processed immediately. Only when the message delay is greater than the queue header message will the message queue be traversed to insert the message, and the message is sorted by the delay time.

Class Message

Message initialization

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

It is recommended to use get () to get a Message object, because Message maintains a message pool whose data structure is a one-way linked list, giving priority to taking data from the pool if no objects are created in the pool. If the Message object already exists, you can use the get (msg) method and eventually call get ().

Message Recycling

void recycleUnchecked() {
    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++;
        }
    }
}

Message recovery does not destroy the Message object, but restores the value of the Message object to its initial value and then puts it back into the pool for use.

Because android frequently uses Message objects, using the "pool" mechanism can reduce the time of creating objects to open up memory, and make more efficient use of memory. Therefore, the "pool" mechanism is applied to the case of frequently used class objects. We often say that the "thread pool" is based on the same principle.

Summary: To use handler mechanism in current threads, first ensure that Looper exists in current threads

Looper.parper() creates a Looper object for the current thread and a MessageQueue object at the same time

Each thread has only one Looper object and one MessageQueue object

Looper.loop() starts the cycle and enters the blocking state (-1) without msg.

Message objects are best obtained through Message. get ().

Handler sends a message to the queue, and if there is no delay in waking up blocking Looper to get the msg, calls msg.targe.dispachMessage to process the message.

Close Activity If there are message s on the stack that are not out of the stack, you need to clear handler.removeMessage(int)

When the subthread no longer uses handler, it calls loop.quit(),loop.quitSafely()

Although seemingly looper loop queue and msg is given to handler, it is actually done by next() of MessageQueue, which also undertakes the queuing of messages and sorts them from small to large in terms of latency. Given the enormous workload of MessageQueue, after Android 2.3, the blocking mechanism of next() method in MessageQueue was transferred to the native layer to deal with, that is, the nativePollOnce (ptr, nextPollTimeout Millis) method we used.

This blog post does not analyze barrier logic (msg.target==null), as well as underlying blocking logic, nor does it analyze asynchronous message logic (msg.isAsynchronous()==true)

Posted by fastidious on Tue, 11 Dec 2018 22:54:05 -0800