directory

  1. Android messaging process
  2. Handler
  3. Message
  4. MessageQueue
  5. Looper
  6. HandleThread
  7. data
  8. harvest

Article a tangent

In addition to “audio and video development tour series”, I want to take time to strengthen my relatively weak Java&Android foundation, which is more in line with my inner pursuit and self-expectation. Parallel began another learning journey, starting from the Handler message mechanism, combined with the process of message mechanism and source code for learning analysis.

First, Android message mechanism flow

We first through the following two pictures to Android message mechanism flow and the relationship between the key classes have an understanding, then we combine the source code for analysis.

The flow of the message mechanism

Handler, Message, MessageQueue, and Looper

Android message mechanism 1-Handler(Java layer)

Second, the Handler

Handler has two main uses:

  1. A scheduled message executes at a point in time;
  2. Communication between different threads

2.1 Global Variables

final Looper mLooper; // Have a reference to Looper final MessageQueue mQueue; // Have a reference to MessageQueue final Callback mCallback; final boolean mAsynchronous; IMessenger mMessenger;Copy the code

2.2 Construction method

    public Handler() {
        this(null, false);
    }
    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }

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

2.3 to get the Message

Public final Message obtainMessage() {return message.obtain (this); } // Basically the same as above, Public final Message obtainMessage(int what) {return message.obtain (this, what); } / /... The other ObtainMessages are similarCopy the code

2.4 Sending Messages

Android message mechanism 1-Handler(Java layer)

Let’s pick out a few sending methods

** sendMessage: Sends a Message, when is the current time ** MessageQueue matches the insertion position based on when

public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } 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; . return enqueueMessage(queue, msg, uptimeMillis); }Copy the code

** POST: Fetch Message from Message reuse pool, set Message Callback**

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

** postAtFrontOfQueue(): Insert the message to the queue head * * by calling sendMessageAtFrontOfQueue join a when 0 message to the queue, which was inserted into the queue head, Note that MessageQueue#enqueueMessage is inserted into the list according to when (when < p. Hen). If there are already multiple messages in the queue with when equal to 0, this new message will be added to the previous one, which is also 0.

public final boolean postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); } public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; . // The third argument is 0, i.e. when for Message is 0, and is inserted into the head of the queue. If there are already multiple messages in the queue with when equal to 0, the new message is added to the previous message with when equal to 0. return enqueueMessage(queue, msg, 0); }Copy the code

2.5 dispatchMessage Is sent

The priorities are as follows: Message callback method callback.run() > Handler callback method McAllback.handlemessage (MSG) > Handler default handleMessage(MSG)

Public void dispatchMessage(@nonnull Message MSG) {if (MSG. Callback! = null) { handleCallback(msg); } else {//Handler's mCallBack priority if (mCallBack! = null) { if (mCallback.handleMessage(msg)) { return; } // The handleMessage(MSG) method has the lowest priority. }}Copy the code

Third, the Message

The global variable

Public int arg1; public int arg2; public Object obj; public long when; Bundle data; Handler target; //Message has a reference to the Handler Runnable callback; //Message has a pointer to next, which can form a one-way list. public static final Object sPoolSync = new Object(); Private static Message sPool; Private static int sPoolSize = 0; private static int sPoolSize = 0; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50;Copy the code

The constructor checks whether there are any messages that can be reused. If there are, the number of reusable messages in the reuse pool is reduced by one and the message is returned. If I don’t have a new one. Note The default maximum number of reuse pools is 50.

Public static Message obtain() {synchronized (sPoolSync) {public static Message obtain() {synchronized (sPoolSync) { = null) {// Fetch the first Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // Clear in-use flag // The number of reusable messages in the reuse pool minus one sPoolSize--; return m; New return new Message(); }Copy the code

// Create an asynchronous Message that is used to generate and change the password for the system to use; // Create an asynchronous Message that is used to generate asynchronous messages; // Create an asynchronous Message that is used to generate asynchronous messages; // Create an asynchronous Message that is used to generate asynchronous messages; Synchronization barriers are introduced in MessageQueue in combination with methods such as Next.

public void setAsynchronous(boolean async) { if (async) { flags |= FLAG_ASYNCHRONOUS; } else { flags &= ~FLAG_ASYNCHRONOUS; } } void recycleUnchecked() { // 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 = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; Synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool; synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool; sPool = this; sPoolSize++; }}}Copy the code

//toString and dumpDebug can Dump message messages and help analyze android.os. message #toString(long)

android.os.Message#dumpDebug

Fourth, the MessageQueue

MessageQueue is a single linked list priority queue. Messages cannot be added directly to MessageQueue, but must be added through Handler and its corresponding Looper.

variable

// the first Message in the list is MessageQueue.Copy the code

** Next: Fetch the next Message to be executed from the Message queue ** If it is a synchronous barrier Message, find the first asynchronous Message in the first queue. If the first Message is executed later than the current time, record how long it will take to start execution; Otherwise it finds the next Message to execute. The loop method of Looper later calls this method from queue.next to get the next Message to execute, which calls the blocking native method nativePollOnce, which is used to “wait” until the next Message is available. If it takes a long time during this call, it means that the corresponding thread has no actual work to do, so there is no ANR, and ANR has nothing to do with this.

The key codes are as follows:

Message next() {final long PTR = mPtr; if (ptr == 0) { return null; }... for (;;) {// Block operations when waiting for nextPollTimeoutMillis, or when the message queue is awakened //nativePollOnce is used to "wait" until the next message is available. If it takes a long time during this call, it means that the corresponding thread has no actual work to do, so there is no ANR, and ANR has nothing to do with this. nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; // Create a new Message pointing to the current Message queue header Message MSG = mMessages; // If it is a synchronous barrier message, find the first asynchronous message in the first queue if (MSG! = null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) {// If the first Message is executed later than the current time, If (now < msg.when) {nextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); } else {// Otherwise fetch the current Message from the list and make the next point in the list point to the next Message mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } // Next = null MSG. Next = null; msg.markInUse(); return msg; }}... / / android. OS. MessageQueue# quit when mQuitting to true / / if you need to quit, executed immediately and return a null Message, Android.os.looper. loop Exits the Looper loop after receiving a null message if (McOntract) {dispose(); return null; }... If (pendingIdleHandlerCount <= 0) {// Note that mBlocked is marked true if no message needs to be executed, This flag is used in enqueueMessage to determine whether nativeWake is invoked to awaken mBlocked = true; continue; }... }... }Copy the code

EnqueueMessage: Inserts a Message into the Message queue

If the Message list is empty, or the inserted Message is executed earlier than the first Message in the Message list, insert directly to the head or find a suitable place in the list for insertion. In general, there is no need to wake up the event queue, except in the following two cases:

  1. There is only one Message that has just been inserted in the Message list, and mBlocked is true, meaning that it is blocking and waking up when a Message is received
  2. The head of the list is a synchronous barrier, and the message is the first asynchronous message

Who wake up? Blocked nativePollOnce in messagequeue.next

The concrete implementation is as follows,

How to find the right place? This involves the insertion algorithm of the linked list: introduce a prev variable that points to P as well as message (if it is executed internally for the first time), then move P to next and compare when with the message to be inserted

The key codes are as follows:

boolean enqueueMessage(Message msg, long when) { ...... synchronized (this) { msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; / / if the news list is empty, or inserted into the Message than the news list first to perform more early, if directly inserted into the head (p = = null | | the when = = 0 | | the when < p.w hen) {MSG. Next = p; mMessages = msg; needWake = mBlocked; } else {// Otherwise find the right place in the list to insert // Normally there is no need to wake up the event queue, unless the head of the list is a synchronization barrier, And this message is the first asynchronous message needWake = mBlocked && p.target == null && msg.isasynchronous (); // The list introduces a prev variable that points to p as well as message (if it is executed inside the for loop for the first time), then moves P to next and compares it with the message to insert when message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; prev.next = msg; } // If an asynchronous message is inserted and the first message in the message list is a synchronous barrier message. // Or the list of messages has only one Message that has just been inserted, and mBlocked is true, i.e., it is being awakened after receiving a Message. Messagequeue.next blocks nativePollOnce if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Take a quick look at native epoll.

NativePollOnce and nativeWake use the ePoll system call, which monitors IO events in file descriptors. NativePollOnce calls epoll_wait on a file descriptor, NativeWake writes an IO operation to the descriptor epoll, which belongs to the IO reuse mode call and calls epoll_wait to wait. The kernel then retrieves the epoll wait thread from the wait state, and the thread continues to process the new message

** removeMessages: ** removeMessages: ** removeMessages: ** removeMessages: ** removeMessages Remove the same MSG eg: msg1.what=0; msg2.what=0; msg3.what=0; msg4.what=1; MSG with what = 0 needs to be removed, i.e. the first three need to be removed

Remove the corresponding message that is not the header in the message list, eg:msg1.what=1; msg2.what=0; msg3.what=0; The message that what is 0 needs to be removed, that is, the subsequent message needs to be removed, which reflects the query and removal algorithm of linked list everywhere

The key codes are as follows:

void removeMessages(Handler h, int what, Object object) { ...... synchronized (this) { Message p = mMessages; // Remove the same MSG eg: msg1.what=0; msg2.what=0; msg3.what=0; msg4.what=1; We need to remove MSG where what is 0, i.e. remove the first three while (p! = null && p.target == h && p.what == what && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } // Remove the corresponding message that is not in the header of the message list, eg:msg1.what=1; msg2.what=0; msg3.what=0; While (p! = null) { Message n = p.next; if (n ! = null) { if (n.target == h && n.what == what && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; }}}Copy the code

** postSyncBarrier: sends synchronization barrier messages **

The synchronization barrier is also a message, but the target of the message is null. The insertion position of the synchronization barrier is not always at the head of the list, but is based on information such as when: if when is not 0, the list is not empty, find the position in the list where the synchronization barrier is inserted; If prev is null, the synchronization message is inserted to the head of the queue.

The key codes are as follows:

/ * * * android. View. ViewRootImpl# scheduleTraversals () * @ param barrier synchronization messages sent when * @ return * / 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++; // The synchronization barrier is also a message, except that the target of the message is null final message MSG = message.obtain (); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when ! = 0) {// If when is not 0 and the message list is not empty, find the position in the message list where the synchronization barrier is inserted. = null && p.when <= when) { prev = p; p = p.next; } } if (prev ! = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else {// If prev is empty, the synchronization message is inserted into the queue header msg.next = p; mMessages = msg; } return token; }}Copy the code

Dump: Sometimes we need to dump the current Looper Message to analyze some problems, such as whether there are too many messages in the Queue. If there are too many messages, the execution of subsequent messages in the Queue will be affected, which may lead to slow logical processing and even ANR. The default reuse pool for MessageQueue is 50, and too many queued messages can also affect performance. Dump Message information can be used for analysis. mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);

 void dump(Printer pw, String prefix, Handler h) {
        synchronized (this) {
            long now = SystemClock.uptimeMillis();
            int n = 0;
            for (Message msg = mMessages; msg != null; msg = msg.next) {
                if (h == null || h == msg.target) {
                    pw.println(prefix + "Message " + n + ": " + msg.toString(now));
                }
                n++;
            }
            pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
                    + ", quitting=" + mQuitting + ")");
        }
    }
Copy the code

Five, the stars

Looper mainly involves the construction, prepare and loop methods, in order to ensure that a thread has only one Looper design, using ThreadLocal and code logic control.

variable

Static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); final MessageQueue mQueue; final Thread mThread;Copy the code

The constructor creates a MessageQueue that corresponds to the Looper when it constructs the Looper

Private Looper(Boolean quitAllowed) {// When creating Looper new MessageQueue mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code

Prepare We can see here how the messaging mechanism ensures that a thread has only one Looper.

Private static void prepare(Boolean quitAllowed) {// Ensure that only one Looper can be created for a thread. SThreadLocal if (sthreadlocal.get ()! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }Copy the code

Loop we analyzed in MessageQueue’s next method that nativePollOnce might block until a message is reached. Exit the Looper loop if next returns a null Message, otherwise dispatch MSG. After execution, the extracted MSG will be added to the recycling pool for reuse. RecycleUnchecked we’ve also analyzed in Message. If it’s not clear, look back.

public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }... for (;;) {// Next is a blocking method, and nativePollOnce can block until the message is retrieved. Message msg = queue.next(); // Receive empty MSG, Loop exit. When will you receive an empty MSG? quit if (msg == null) { // No message indicates that the message queue is quitting. return; } / distributed/MSG, MSG. The target is the Handler, that is to invoke the Handler dispatchMessage distributed message MSG. Target. DispatchMessage (MSG); ... / / MSG MSG. J recycleUnchecked (); }Copy the code

Six, HandleThread

A HandlerThread is a Thread with a Looper.

The global variable

public class HandlerThread extends Thread { int mPriority; // Thread priority int mTid = -1; // thread id Looper mLooper; private Handler mHandler; . }Copy the code

A constructor

public HandlerThread(String name) { super(name); / / set the priority of a thread when used to run the Process. The setThreadPriority (mPriority); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; }Copy the code

The run method calls prepare and loop for Looper and sets up the Looper environment

@override public void run() {// thread id mTid = process.mytid (); Looper.prepare(); // Call the prepare method of Looper and add the only Looper associated with the current thread to the sThreadLocal. Synchronized (this) {// Obtain Looper from sThreadLocal mLooper = looper.mylooper (); notifyAll(); } // Set the thread priority. The default value is THREAD_PRIORITY_DEFAULT or THREAD_PRIORITY_BACKGROUND. According to the specific scene set Process. SetThreadPriority (mPriority); OnLooperPrepared (); // start looper looper.loop (); mTid = -1; }Copy the code

The general flow for using HandlerThread is as follows

Looper HandlerThread HandlerThread = new HandlerThread(" XXX "); handlerThread.start(); Handler Handler = new Handler(handlerThread.getLooper()); handler.sendMessage(msg);Copy the code

A new HandlerThread needs to be created every time a Handler is used. And the implementation Message can be executed in time, not blocked by the previous Message in the queue; This is really an interesting and challenging thing to do.

Seven, data

  1. The Android source code
  2. Principle analysis and application scenarios of ThreadLocal
  3. Android Messaging 1-Handler(Java Layer)
  4. Android Handler mechanism for Comprehensive parsing (Final) : FAQ summary
  5. Does Handler really get it?
  6. Android synchronization barrier? Blocking wake up? Hidden secrets in Handler
  7. Epoll summary for IO multiplexing
  8. Are you clear about the 15 questions about Handler?
  9. Android system blood: Handler
  10. Confident, this is ThreadLocal analysis at its best
  11. Android Handler: Take you through the Handler mechanism source code
  12. Android handler. removeMessage removes all postDelayed issues
  13. Android MessageQueue nativePollOnce

Eight, harvest

Through the Android message mechanism of the source code analysis comb, make clear

  1. The flow of Android messaging mechanism;
  2. How to ensure that one thread corresponds to one Looper
  3. Data structure design of single linked list of Message and reuse pool mechanism of Message
  4. MessageQueue rejoins Message and retrieves Message implementations, as well as involving blocking wake up and synchronization barriers.

The analysis of ThreadLocal and native layer has not been thoroughly understood, so we will analyze and study it in the next two chapters

Thank you for reading our next article on how to use ThreadLocal and its Android messaging mechanism. Welcome to communicate