Messaging mechanism in Android

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

Handler message mechanism diagram:

Handler class diagram:

Explanation of the above diagram:

  1. Take the sendMessage () function of the Handler as an example. When a message is sent, it is added to the MessageQueue message queue.
  2. Looper is responsible for traversing the message queue and distributing messages in the queue to non-corresponding handlers for processing.
  3. The message is processed in the Handler’s handlerMessage method, which completes the sending and processing of a message.

As can be seen from the figure, the four most important objects of the Android Handler Message mechanism are Handler, Message, MessageQueue, and Looper.

How ThreadLocal works

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 obtain the stored data. Other threads cannot obtain the stored objects. Let’s verify that ThreadLocal access does what we just said.

  • Child thread store, child thread fetch

     	// Code tests
     	new Thread("thread-1") {@Override
                public void run(a) {
                    ThreadLocal<String> mThread_A = new ThreadLocal();
                    mThread_A.set("thread-1");
                    System.out.println("mThread_A :"+mThread_A.get());
    
                }
            }.start();
    
    	// Print the result
    	mThread_A :thread-1
    Copy the code
  • The main thread stores, the child thread fetch

    	// The main thread stores, the child thread fetches
    	final ThreadLocal<String> mThread_B = new ThreadLocal();   
    	mThread_B.set("thread_B");      
    	new Thread(){
                @Override
                public void run(a) {
                    System.out.println("mThread_B :"+mThread_B.get());
                }
            }.start();
    
    	// Print the result
    	mThread_B :null
    Copy the code
  • Main thread save, main thread take

    	// the main thread is stored, the main thread is fetched
            ThreadLocal<String> mThread_C = new ThreadLocal();
            mThread_C.set("thread_C");
            System.out.println("mThread_C :"+mThread_C.get());
    
    	// Print the result
    	mThread_C :thread_C
    Copy the code

Is the answer the same as we said above, so why is it so? Now, what does the ThreadLocal source code actually do?

The main components of a ThreadLocal function are set, get, so let’s start with set, get.

ThreadLocal set(T):

(figure 1)

(figure 2)

(figure 3)

(figure 4)

GetMap (currentThread) getMap(currentThread) getMap(currentThread)

If the ThreadLocalMap of the current thread is NULL, then createMap is created. The ThreadLocalMap here can be thought of as a collection object for the moment, and underneath it (Figure 4) is the added data of an array implementation.

ThreadLocal T get():

The get() function already explains why data stored in different threads is missing. Since storage is stored in the current thread, fetching data is also fetched in the current thread, so it is not possible to retrieve. With the question we found the answer. Are you a little excited? (^ del ^)

Android message mechanism source analysis

Here we directly look at the source code, I look at the source code process.

  1. Create globally unique Looper objects and globally unique MessageQueue message objects.

  2. Create a Handler in the Activity.

  3. Handler sendMessage Indicates the direction to send a message.

  4. Handler Message processing.

Message blocking and latency

Blocking and delay

Looper blocking is mainly achieved by MessageQueue, Block in MessageQueue -> next() nativePollOnce(PTR, nextPollTimeoutMillis), Wake up MessageQueue -> enqueueMessage() -> nativeWake(mPtr). It mainly relies on the Looper epoll base of native layer.

Blocking and latency, mainly due to next() ‘s nativePollOnce(PTR, nextPollTimeoutMillis) calling native methods to manipulate the pipe, NextPollTimeoutMillis determines whether to block. When nextPollTimeoutMillis is 0, it means no blocking; when nextPollTimeoutMillis is -1, it means blocking until wake up; other times indicate delay.

Wake up the

EnqueueMessage () @messagequeue

Block -> Wake up message toggle

conclusion

If the main thread MessageQueue has no message, it will block in the nativePollOnce() method of the Loop queue.next(). At this time, the main thread will release CPU resources and go to sleep. Not fired until the next message arrives or when there is a message, waking up the main thread by writing data to the pipe writer.

The epoll mechanism adopted here is an IO multiplexing mechanism, which can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), it immediately notifies the corresponding program to carry out read or write operations, which is essentially synchronous I/O, that is, read and write is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources.

Delay team

The code above reorders the pool of Message objects, following the rules (when from small to large).

There are two ways to exit the for loop

  1. P == null indicates that the last object pool has been run and no recirculation is required.
  2. When the next message is less than the previous one, it immediately exits the loop (regardless of whether all messages in the object pool have been traversed) and reorders.

This is the end of the Handler source code analysis, so let’s look at some of the most common questions that may be asked during an interview.

FAQ Analysis

What is the root cause of not updating the UI in child threads?

MThread is the main thread, and it checks if the current thread is the main thread, so why isn’t this checked inside onCreate? The cause of this problem occurs during the Activity lifecycle. In onCreate, the UI is being created and the interface is not visible to the user until it is visible after onStart, and then after onResume, the page can interact. So in some sense, in the onCreate method, you’re not updating the UI, you’re just configuring the UI, or setting UI properties. The viewrootimpl.checkThread () will not be called at this time because the ViewRootImpl is not created. After the onResume method, the view wrootimpl is created. This time to hand in the user interface is to update the UI.

SetContentView knowledge builds the View tree and does no rendering work (the real rendering work is done after onResume). It was also the View tree is established, so that we can pass the findViewById () to get to View objects, but due to the job is not to render View, also is not implemented ViewRootImpl. PerformTransversal. The View does not perform onMeasure () either. If you retrieve view.getheight ()/view.getwidth () from onResume(), the result is always 0.

Why does a Looper loop on the main thread not raise ANR?

In simple terms, when the main thread MessageQueue has no message, it blocks the nativePollOnce() method in the loop queue.next(). At this point, the main thread releases CPU resources and goes to sleep until the next message arrives or a transaction occurs. Wake up the main thread by writing data to the PIPE pipe. The epoll mechanism is used here, which is an IO multiplexing mechanism.

Why is Looper in Handler constructor not new directly?

If you create a new Looper() in the Handler constructor, you may not be able to ensure that Looper is unique.

Why should MessageQueue be initialized in the Looper private constructor?

Since a Thread is bound to only one Looper, initialization in the Looper constructor ensures that the mQueue is also unique, Thread per Looper per mQueue.

In which thread is the Handler. Post logic executed? Is it determined by the thread of Looper or the thread of Handler?

Determined by the thread in which Looper resides. The logic is in the looper.loop () method, which takes the message from the MessageQueue and executes its logic, which is executed in the Looper, so it is determined by the thread of the Looper.

Messagequeue.next () blocks because it found a delayed message. So why aren’t subsequent non-delayed messages blocked?

See Message blocking and Delay -> Wake up

How does the Handler dispatchMessage () process messages?

  1. Belongs to the Runnable interface.

  2. Called in the following code form.

        private static Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return true; }});Copy the code
  3. If the first and second steps are not enough, go directly to the following handlerMessage and refer to the code implementation below

        private static Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg); }};Copy the code

You can also view the dispatchMessage execution status in debug mode.

Implement your own simple Handler architecture

The main implementation of test code

Code transfer array