Source code analysis of handler mechanism

Source code analysis of handler mechanism

I always think I have a thorough understanding of handler mechanism. An accidental opportunity found that after working for a period of time, my wife forgot all about it and combed it again today. I hope it will be more in-depth.

Summarize a few core classes first, so that you don't get a clue when you look at the record later.
ActivityThread

Loop

ThreadLocal

MessageQueue

Message

Handler

Now that we have a good directory, what we need to do is to define what these classes are used for.

What is ActivityThread?

Some people say that the main thread or UI thread is wrong. Let's look at the definition of ActivityThread.

 public final class ActivityThread {

ActivityThread is just a simple class and thread. Thread seems to have nothing to do with it.

So why would someone say ActivityThread is the main thread? The simple reason is that Zyget will be fork ed by a new process, which is the process that app runs when app starts. The process will load the ActivitThread class after it has been built, so according to the java foundation, we will run the static main function mian with a new ActivityThread, so ActivityThread starts to run in the main thread (here may also be my understanding is not in place, if there is a good way to understand also ask for advice).

Here's the main 5.0 source code. If you're upset, you can skip the code block directly because there are four sentences in it that are useful to us.

5184 public static void main(String[] args) {
5185        SamplingProfilerIntegration.start();
5186
5187        // CloseGuard defaults to true and can be quite spammy.  We
5188        // disable it here, but selectively enable it later (via
5189        // StrictMode) on debug builds, but using DropBox, not logs.
5190        CloseGuard.setEnabled(false);
5191
5192        Environment.initForCurrentUser();
5193
5194        // Set the reporter for event logging in libcore
5195        EventLogger.setReporter(new EventLoggingReporter());
5196
5197        Security.addProvider(new AndroidKeyStoreProvider());
5198
5199        // Make sure TrustedCertificateStore looks in the right place for CA certificates
5200        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
5201        TrustedCertificateStore.setDefaultUserDirectory(configDir);
5202
5203        Process.setArgV0("<pre-initialized>");
5204
5205        Looper.prepareMainLooper();
5206
5207        ActivityThread thread = new ActivityThread();
5208        thread.attach(false);
5209
5210        if (sMainThreadHandler == null) {
5211            sMainThreadHandler = thread.getHandler();
5212        }
5213
5214        AsyncTask.init();
5215
5216        if (false) {
5217            Looper.myLooper().setMessageLogging(new
5218                    LogPrinter(Log.DEBUG, "ActivityThread"));
5219        }
5220
5221        Looper.loop();
5222
5223        throw new RuntimeException("Main thread loop unexpectedly exited");
5224    }
5225}

An ActivityThread is created in line 5207, 5208 and the current data is initialized into ActivityThread

Then focus on

5205 Loper. prepareMainLooper (); it creates a Looper instance and saves it in ThreadLocal.

Looper.loop(); opens Looper's loop for processing

This is the end of the process, and the main thread has entered a dead cycle; PS: After the life cycle management, ui processing is processed in the loop.

What is Loop?

The Loop core is a dead loop for looping ** to process ** MessageQueue information
Notice the endless cycle and processing of the words I use, and you'll see why I use them.

Let's start with the concept that there is no Looper by default in every thread. If you want to use it, you have to initialize a Looper for the thread itself, and the main thread initializes a Looper when it is created. That's why the main thread can use the Handler sometimes.

In the previous section, I said that 5027 and 5028 need attention.

  • Let's start with Looper.prepareMainLooper() here in 5027.
87     public static void prepareMainLooper() {
88         prepare(false);
89         synchronized (Looper.class) {
90             if (sMainLooper != null) {
91                 throw new IllegalStateException("The main Looper has already been prepared.");
92             }
93             sMainLooper = myLooper();
94         }
95     }

Initialize it in the first sentence prepare(false)

74     private static void prepare(boolean quitAllowed) {
75         if (sThreadLocal.get() != null) {
76             throw new RuntimeException("Only one Looper may be created per thread");
77         }
78         sThreadLocal.set(new Looper(quitAllowed));
79     }

The prepare function also tells us very clearly that a new non-existent Looper is placed in ThreadLocal.

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

Looper created a MessageQueue for itself and saved the current thread, so Looper was created.

  • Then the Looper.loop() of 5221 is promptly opened; Looper's loop is opened for processing.

Another wavelength code is just the same as the one you don't want to see. It's useful not to read a few sentences (in fact, there aren't many lines besides comments).

109    public static void loop() {
110        final Looper me = myLooper();
111        if (me == null) {
112            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
113        }
114        final MessageQueue queue = me.mQueue;
115
116        // Make sure the identity of this thread is that of the local process,
117        // and keep track of what that identity token actually is.
118        Binder.clearCallingIdentity();
119        final long ident = Binder.clearCallingIdentity();
120
121        for (;;) {
122            Message msg = queue.next(); // might block
123            if (msg == null) {
124                // No message indicates that the message queue is quitting.
125                return;
126            }
127
128            // This must be in a local variable, in case a UI event sets the logger
129            Printer logging = me.mLogging;
130            if (logging != null) {
131                logging.println(">>>>> Dispatching to " + msg.target + " " +
132                        msg.callback + ": " + msg.what);
133            }
134
135            msg.target.dispatchMessage(msg);
136
137            if (logging != null) {
138                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
139            }
140
141            // Make sure that during the course of dispatching the
142            // identity of the thread wasn't corrupted.
143            final long newIdent = Binder.clearCallingIdentity();
144            if (ident != newIdent) {
145                Log.wtf(TAG, "Thread identity changed from 0x"
146                        + Long.toHexString(ident) + " to 0x"
147                        + Long.toHexString(newIdent) + " while dispatching to "
148                        + msg.target.getClass().getName() + " "
149                        + msg.callback + " what=" + msg.what);
150            }
151
152            msg.recycleUnchecked();
153        }
154    }

Focus

final Looper me = myLooper(); gets the Loper of the current thread
121 for (;;) Dead loop to process information in message

Message msg = queue.next() of 122; get information about messageQueue (how do we get it here and then MessageQueue again)

135 msg.target.dispatchMessage(msg); Processing information (how? It must be handler. It's handler in tag. After what handler does, handler will talk about it in detail.

160    public static Looper myLooper() {
161        return sThreadLocal.get();
162    }

myLooper was obtained in ThreadLocal

What is ThreadLocal?

Every time ThreadLocal looks at the name, the first response is the xx thread. It's not easy to call yourself a thread here, "Your sister, threads are everything. He's clearly a storage class." Different copies of different threads of the storage class do not affect each other, and the scope of the entire thread is within the scope of the whole thread.

  • Let's start with set
179    public void set(T value) {
180        Thread t = Thread.currentThread();
181        ThreadLocalMap map = getMap(t);
182        if (map != null)
183            map.set(this, value);
184        else
185            createMap(t, value);
186    }

I didn't look at it until I saw it. I saved it with map. But that's not the case... static class ThreadLocalMap {.... Nima Map is not directly related to Map class or an internal class. You fucking play with me. If you cut out the interview and ask me the details, I will answer by Map instead of fucking? In fact, it was actually used by an interviewer.

416 private void set(ThreadLocal key, Object value) {
417
418            // We don't use a fast path as with get() because it is at
419            // least as common to use set() to create new entries as
420            // it is to replace existing ones, in which case, a fast
421            // path would fail more often than not.
422
423            Entry[] tab = table;
424            int len = tab.length;
425            int i = key.threadLocalHashCode & (len-1);
426
427            for (Entry e = tab[i];
428                 e != null;
429                 e = tab[i = nextIndex(i, len)]) {
430                ThreadLocal k = e.get();
431
432                if (k == key) {
433                    e.value = value;
434                    return;
435                }
436
437                if (k == null) {
438                    replaceStaleEntry(key, value, i);
439                    return;
440                }
441            }
442
443            tab[i] = new Entry(key, value);
444            int sz = ++size;
445            if (!cleanSomeSlots(i, sz) && sz >= threshold)
446                rehash();
447        }

Looking at the code, we know that there is a table array stored in it!!!
You should know 425 key. threadLocalHashCode & (len-1) when you look at the hashMap source code; what you do is take the remainder and use the hashCode of threads to calculate it.
The overall function of this code block is to use the remainder as the following table. When conflicts occur, the subscript is moved backwards from the increase to resolve conflicts. At the end of the judgment, and then determine whether rehash expansion. In principle, it is similar to hashMap, but hashMap resolves conflicts by adding lists to arrays. Conflicts are resolved in different ways. I'm not going to elaborate here.

ps: ThreadLocal essentially means that when a thread calls, it gets not one variable with different variables corresponding to the current process, thus realizing the isolation of thread variables, and the scope of variables in the thread becomes the whole thread.

What is MessageQueue?

* MessageQueue* Manages Message Queues From the Looper code above, you can see that each Looper holds a MessageQueue object

  • The most important method of a message queue is definitely insertion and extraction.

    • enqueueMessage() method --> insert
    • next() method ---> take out and remove

The first thing to see is how to put it in MessageQueue, and why to put it in this way, you can see handler after you see it.

boolean enqueueMessage(Message msg, long when) {
316        if (msg.target == null) {
317            throw new IllegalArgumentException("Message must have a target.");
318        }
319        if (msg.isInUse()) {
320            throw new IllegalStateException(msg + " This message is already in use.");
321        }
322
323        synchronized (this) {
324            if (mQuitting) {
325                IllegalStateException e = new IllegalStateException(
326                        msg.target + " sending message to a Handler on a dead thread");
327                Log.w("MessageQueue", e.getMessage(), e);
328                msg.recycle();
329                return false;
330            }
331
332            msg.markInUse();
333            msg.when = when;
334            Message p = mMessages;
335            boolean needWake;
336            if (p == null || when == 0 || when < p.when) {
337                // New head, wake up the event queue if blocked.
338                msg.next = p;
339                mMessages = msg;
340                needWake = mBlocked;
341            } else {
342                // Inserted within the middle of the queue.  Usually we don't have to wake
343                // up the event queue unless there is a barrier at the head of the queue
344                // and the message is the earliest asynchronous message in the queue.
345                needWake = mBlocked && p.target == null && msg.isAsynchronous();
346                Message prev;
347                for (;;) {
348                    prev = p;
349                    p = p.next;
350                    if (p == null || when < p.when) {
351                        break;
352                    }
353                    if (needWake && p.isAsynchronous()) {
354                        needWake = false;
355                    }
356                }
357                msg.next = p; // invariant: p == prev.next
358                prev.next = msg;
359            }
360
361            // We can assume mPtr != 0 because mQuitting is false.
362            if (needWake) {
363                nativeWake(mPtr);
364            }
365        }
366        return true;
367    }

The first part is to determine that the core of some states is that 336 lines begin to put message into the list, and this position is put into the sum for(;) block of code, which is not a dead loop. Because the internal is a single linked list, the single linked list is not the end of the loop list, so this for will not be a dead loop.
The purpose of this is to insert the message into the end of the list.

Then take it out and remove it. Is this a familiar way to look at it?

The reason is that the Looper.loop() method in the Main function of ActivityThread has been called.

  • The following is a large source code, which involves two major parts: the Native layer and the Java layer. Here, I have not talked too much about the Native layer. Let's just look at the Java layer.
127    Message next() {
128        // Return here if the message loop has already quit and been disposed.
129        // This can happen if the application tries to restart a looper after quit
130        // which is not supported.
131        final long ptr = mPtr;
132        if (ptr == 0) {
133            return null;
134        }
135
136        int pendingIdleHandlerCount = -1; // -1 only during first iteration
137        int nextPollTimeoutMillis = 0;
138        for (;;) {
139            if (nextPollTimeoutMillis != 0) {
140                Binder.flushPendingCommands();
141            }
142
143            nativePollOnce(ptr, nextPollTimeoutMillis);
144
145            synchronized (this) {
146                // Try to retrieve the next message.  Return if found.
147                final long now = SystemClock.uptimeMillis();
148                Message prevMsg = null;
149                Message msg = mMessages;
150                if (msg != null && msg.target == null) {
151                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
152                    do {
153                        prevMsg = msg;
154                        msg = msg.next;
155                    } while (msg != null && !msg.isAsynchronous());
156                }
157                if (msg != null) {
158                    if (now < msg.when) {
159                        // Next message is not ready.  Set a timeout to wake up when it is ready.
160                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
161                    } else {
162                        // Got a message.
163                        mBlocked = false;
164                        if (prevMsg != null) {
165                            prevMsg.next = msg.next;
166                        } else {
167                            mMessages = msg.next;
168                        }
169                        msg.next = null;
170                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
171                        return msg;
172                    }
173                } else {
174                    // No more messages.
175                    nextPollTimeoutMillis = -1;
176                }
177
178                // Process the quit message now that all pending messages have been handled.
179                if (mQuitting) {
180                    dispose();
181                    return null;
182                }
183
184                // If first time idle, then get the number of idlers to run.
185                // Idle handles only run if the queue is empty or if the first message
186                // in the queue (possibly a barrier) is due to be handled in the future.
187                if (pendingIdleHandlerCount < 0
188                        && (mMessages == null || now < mMessages.when)) {
189                    pendingIdleHandlerCount = mIdleHandlers.size();
190                }
191                if (pendingIdleHandlerCount <= 0) {
192                    // No idle handlers to run.  Loop and wait some more.
193                    mBlocked = true;
194                    continue;
195                }
196
197                if (mPendingIdleHandlers == null) {
198                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
199                }
200                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
201            }
202
203            // Run the idle handlers.
204            // We only ever reach this code block during the first iteration.
205            for (int i = 0; i < pendingIdleHandlerCount; i++) {
206                final IdleHandler idler = mPendingIdleHandlers[i];
207                mPendingIdleHandlers[i] = null; // release the reference to the handler
208
209                boolean keep = false;
210                try {
211                    keep = idler.queueIdle();
212                } catch (Throwable t) {
213                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
214                }
215
216                if (!keep) {
217                    synchronized (this) {
218                        mIdleHandlers.remove(idler);
219                    }
220                }
221            }
222
223            // Reset the idle handler count to 0 so we do not run them again.
224            pendingIdleHandlerCount = 0;
225
226            // While calling an idle handler, a new message could have been delivered
227            // so go back and look again for a pending message without waiting.
228            nextPollTimeoutMillis = 0;
229        }
230    }

It's time for code analysis again.

  • In line 143, nativePollOnce (ptr, nextPoll Timeout Millis); this is a nativelayer that handles more than one thing. First, it concludes: "native layer also has a similar message mechanism, which handles natives first, then java layer. There are also distinctions in java layer, that is, asynchronous and synchronous. Final deal with idle handler"
    Here's one. address Look, there's a mention in it.

  • The comments in lines 150-156 see 151 refer to being opened by a barrier to get the first asynchronous Message. Here is to find the asynchronous Message and then process the Message
    Here Message is also divided into synchronous and asynchronous. What is the difference between synchronous and asynchronous? This is what the Message says later.
    Careful friends may find that 150 lines of code are judged by msg.target==null, but before mentioning that a mouthful of msg.target should contain handler's ah null. Here's a Barrier concept.
    Barrier is a special message whose target is null. (Only Barrier's target can be null, and if we try to set Message's target to null, we will report an exception.) What's the effect? Used to distinguish synchronous messages from interceptors and release asynchronous messages?
    Here's another one. address To expand, there are introductions, although not very detailed, can be used as an introduction reference.

  • The function of 157-176 is to compare the current time with the task time to see if the task time has arrived. If it arrives, it will return to the current task. If not, it will continue to go down the dead cycle.

  • 178-182 You can see from the comment that the value to exit mQuitting is set in quit(), of course, before setting it, you need to determine whether mQuitAllowed==true is set at the beginning of creation.

  • After that, it runs the acquisition of idle messages. It's understandable that there are also some functions in Message Queue that are specifically designed to set up idle messages, not to mention here.

PS: All in all, it belongs to the whole messageQueue acquisition process. Suddenly, the more it is written, the more unknown it is. Otherwise, one handler will lose its head and the others will play a ball. Then there's time for improvement.

What is Message?

Message messages are stored in MessageQueue as a medium of storage type for acquisition and processing.

There is a comment on the construction method of Message /** Constructor (but the preferred way to get a Message is to call {@link get () Message. get ()}).*/
Message does not recommend using new to create a message. It suggests using obtain to create a message. In short, there will be some initialization methods in the obtain method to find the m.target=h which is useful to us and is also available in almost every obtainment overload method.

  • m stands for message
  • h stands for Handler
    This also proves that the handler is in the msg.target target. dispatchMessage (msg) in the Looper.loop() method and that msg.target.dispatchMessage(msg) will distribute msg to the handler's dispatchMessage for distribution processing.
118  /**
119     * Return a new Message instance from the global pool. Allows us to
120     * avoid allocating new objects in many cases.
121     */
122 public static Message obtain() {
123        synchronized (sPoolSync) {
124            if (sPool != null) {
125                Message m = sPool;
126                sPool = m.next;
127                m.next = null;
128                m.flags = 0; // clear in-use flag
129                sPoolSize--;
130                return m;
131            }
132        }
133        return new Message();
134    }

In this case, I'll find out that the Message Processing MessageQueue has an sPool global information pool to maintain. So the teacher is knocking on the blackboard. What is the use of this information pool? Its function is reuse. As can be seen from the above code block, sPool is a linked list structure. What's stored in this list? What's saved is the reusable Message. After reading the following, you can see that the list must have a memory and a acquisition process. The code above is taken. So what's there? It's below when looper.loop() processes dispatchMessage. Use the following functions.

114   private static final int MAX_POOL_SIZE = 50;
...

291    void recycleUnchecked() {
292        // Mark the message as in use while it remains in the recycled object pool.
293        // Clear out all other details.
294        flags = FLAG_IN_USE;
295        what = 0;
296        arg1 = 0;
297        arg2 = 0;
298        obj = null;
299        replyTo = null;
300        sendingUid = -1;
301        when = 0;
302        target = null;
303        callback = null;
304        data = null;
305
306        synchronized (sPoolSync) {
307            if (sPoolSize < MAX_POOL_SIZE) {
308                next = sPool;
309                sPool = this;
310                sPoolSize++;
311            }
312        }
313    }

From the last 306 to 313 lines, you can see that when the number of sPools is not greater than 50, the used message will be put back in the sPool and put in the first place. When it's over 50, it's not forgotten that it's stored in pool. Because of space constraints, I don't write much. Look here.
So most of the core stuff of message is done.

What is Handler?

Handler is translated as a handler. It is obvious from translation that handler carries message processing. If you have read the above chapters, you must know handler.dispatchMessage handles messages.

93     public void dispatchMessage(Message msg) {
94         if (msg.callback != null) {
95             handleCallback(msg);
96         } else {
97             if (mCallback != null) {
98                 if (mCallback.handleMessage(msg)) {
99                     return;
100                }
101            }
102            handleMessage(msg);
103        }
104    }

From the above code, it can be seen that if there is handler Callback, it will be handler Message to deal with this method, I will not say, after all, most people who have used handler have certainly realized this method. Then the logic for dealing with this is cleared, and looper opens the loop. MessageageQueue is used to get the message, which is then retrieved from message.target and handler calls dispatchMessage to process the message based on the resulting handler.

So it feels like there's something missing. That's how messages are inserted into messageQueue.

When we use handler message mechanism, we must have been to post or post method postDelayed and other methods, in fact, the internal are gathered in one place. Let's take post as an example

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

There's a place where it's easy to ignore the getPostMessage method.

725    private static Message getPostMessage(Runnable r) {
726        Message m = Message.obtain();
727        m.callback = r;
728        return m;
729    }

See, the legendary Message has been created.

565    public final boolean More ...sendMessageDelayed(Message msg, long delayMillis)
566    {
567        if (delayMillis < 0) {
568            delayMillis = 0;
569        }
570        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
571    }

Here, the sendMessageAtTime is invoked after the time is converted to the corresponding departure time, and this method is the final invocation method of post and other methods.

592    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
593        MessageQueue queue = mQueue;
594        if (queue == null) {
595            RuntimeException e = new RuntimeException(
596                    this + " sendMessageAtTime() called with no mQueue");
597            Log.w("Looper", e.getMessage(), e);
598            return false;
599        }
600        return enqueueMessage(queue, msg, uptimeMillis);
601    }

To insert into MessageQueue

626    private boolean More ...enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
627        msg.target = this;
628        if (mAsynchronous) {
629            msg.setAsynchronous(true);
630        }
631        return queue.enqueueMessage(msg, uptimeMillis);
632    }

Before inserting a message like MessageQueue, the target of the message is assigned and handler is passed in. The code break also confirms what was said before. You should know the process after that.

Summarize the overall process of handler
***sendMessage->sendMessageDelayed->sendMessageAtTime->enqueueMessage

At this point, the distribution mechanism of the whole message flow is to sort out a flow chart, but there is no place to access the picture for the time being before adding it.

Expanding a Knowledge Point
1161 private class H extends Handler in ActivityThread
It defines some life cycle related stuff. Look at the right and then have time to write the app startup process.

Use: How to use your own Looper? Official examples

  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

If you have the patience to write so much code, you should have a holistic understanding of the messaging mechanism.

Keywords: Java

Added by AlanG on Tue, 16 Jul 2019 23:29:19 +0300