Android has a thorough grasp of Handler. Just look here

Introduction to Handler

         Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each handler instance is associated with a thread and its Message queue. When you create a new handler, it will be bound to a Looper. It delivers messages and Runnable objects to the Looper's Message queue and executes them on the Looper's thread.

Handler has two main purposes:

  • 1. Arrange the execution of messages and runnable objects at some time in the future;

  • 2. Queue operations to be performed on a different thread than your own.

         The main scenario is that when the sub thread completes the time-consuming operation, it sends a Message to the main thread through the Handler to refresh the UI interface. In this article, let's understand the source code implementation of Handler's sending Message and processing Message.

         When analyzing the source code, it is best to find an appropriate entry point. One entry point of the Handler source code is its default constructor.

Analysis source code

new Handler()

    public Handler() {
        this(null, false);
    }
    
    public Handler(@Nullable Callback callback, boolean async) {
        //If leakage is found, the initial value is false. Don't look down
        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());
            }
        }
        //Note 1
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //Note 2
        mQueue = mLooper.mQueue;
        //null
        mCallback = callback;
        //false
        mAsynchronous = async;
    }

         The overloaded constructor is called in the parameterless constructor and null and false are passed in respectively. Two global variables are assigned values in the construction method: mloop and mQueue. Mloop is obtained through Looper. mQueue is obtained through mloop.mQueue. That means they all came to Looper. Let's look at myLooper first.

Looper.myLooper()

    final MessageQueue mQueue;
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

sThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

         It can be seen that myLooper passes through the stub in a thread local variable, and then mQueue is a global variable in loop, and the type is MessageQueue.

MessageQueue: save the list of messages to be scheduled by Looper. Message is not added directly to MessageQueue, but through the Handler object associated with Looper. You can use Looper.myQueue() to retrieve the MessageQueue of the current thread.

Introduction to Looper

         By default, a thread has no message loop associated with it; To create one, call prepare in the thread that runs the loop, then circle it to process the message until the loop stops.

         Most interactions with message loops are done through the Handler class.

         This is a typical example of loop thread implementation. Using the separation of prepare and loop, an initial Handler is created to communicate with loop.

class LooperThread extends Thread {
       public Handler mHandler;
 
       public void run() {
           Looper.prepare();
 
           mHandler = new Handler(Looper.myLooper()) {
               public void handleMessage(Message msg) {
                   //  Processing incoming messages here
               }
           };
 
           Looper.loop();
       }
   }

         The entry function to start a java program is the main method, but when the main function is executed, the program stops running, that is, the process will terminate automatically.

         However, when we open an Activity, as long as we don't press the return key, the Activity will always be displayed on the screen, that is, the process in which the Activity is located will always be running. In fact, Looper internally maintains an infinite loop to ensure that the App process continues.

Looper initialization

         An ActivityThread will be passed in the Activity.attach() method. The main method of ActivityThread is the entry of a new App process.

ActivityThread.main()

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        //  Install selective system call interception
        AndroidOs.install();
        //  Disable CloseGuard
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        //ensure   TrustedCertificateStore   lookup   CA   Correct location of certificate
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        //  Call the mainline module initialization of each process.
        initializeMainlineModules();
        Process.setArgV0("<pre-initialized>");
        //Key: Note 1
        //Initializes the of the current process   Looper   object
        Looper.prepareMainLooper();
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        //Key: Note 2
        //call   Looper   of   loop   Method to open an infinite loop.
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

         Note 1: initialize the Looper object of the current process;

         Note 2: call Looper's loop method to open an infinite loop (as described below).

Looper.prepareMainLooper()

    public static void prepareMainLooper() {
        //Note 1: create a Looper
        //Next, post the method
        prepare(false);
        //Add a synchronization method object lock
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //This will be seen after the prepare is executed
            //Note 1
            sMainLooper = myLooper();
        }
    }

Looper.prepare()

    private static void prepare(boolean quitAllowed) {
        //Determine whether it has been bound   Looper   object
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

The Looper.prepare method is actually a new Looper. The core lies in setting the Looper from new to the thread local variable sThreadLocal.set(looper). That is, the created Looper is bound to the current thread.

Note: before creating the Looper object, you will judge whether the Looper object has been bound in sthreadlocal. If so, an exception will be thrown. The purpose of this line of code is to ensure that the Looper.prepare() method can only be called once in a thread.

new Looper()

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

         Looper initializes the message queue MessageQueue object in the constructor.

         After the prepare method is executed, the myloop () method will be called in Looper.prepareMainLooper to take the Looper object from sThreadLocal and assign it to the sMainLooper variable.

Looper.prepare() can only be called once

         As you can see from the above code, Activity has called Looper.prepare() once in Looper.prepareMainLooper() when it was created. I decided to call Looper.prepare() again in onCreate(), and wish me well. Now I'm going to add a line of code to MainActivity

@Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Looper.prepare();
    }

The result was tragic:

be careful:

  • The prepare method can only be called once in a thread;

  • Looper's construction method can only be called once in a thread;

  • MessageQueue is initialized only once in a thread.

Conclusion: that is to say, there will only be one MessageQueue object in the UI thread, and subsequent messages sent through the Handler will be sent to this MessageQueue.

What does Looper do?

         To sum up, what Looper does is: constantly take out messages from the MessageQueue, and then process the tasks specified in the Message.

         Back to the origin, in the main method of ActivityThread, in addition to calling Looper.prepareMainLooper to initialize the Looper object, the Looper.loop method is also called to start the infinite loop. The main function of Looper is completed in this loop.

Looper.loop()

    public static void loop() {
        //take out   Looper   Object and assign it to   me  
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;
        ...
        //The following is a dead circle. You can't think of it if you go in.
        for (;;) {
            //Note 1
            //call   MessageQueue   of   next   Method take out   Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            try {
                //Note 2
                //msg is not null, please process it
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }

         The above code indicates that an endless loop is executed in the loop method, which is also the reason why an Android App process can run continuously.

         Note 1: continuously call the next method of MessageQueue to get the Message.

         Note 2: if the message is not null, subsequent processing will be performed at. Specifically, the target object is extracted from Message and its dispatchMessage method is invoked to handle Message itself. Who is target?

Message.target

public final class Message implements Parcelable {
    ...
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;
    /*package*/ Bundle data;
    @UnsupportedAppUsage
    /*package*/ Handler target;
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;
    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    ...
}

         After viewing, it is actually a Handler. Let's take another look at the dispatchMessage method of the Handler

Handler.dispatchMessage()

    /**
     * Handle system messages here.
     * System messages are processed here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    /**
     * Subclasses must implement this to receive messages.
     * Subclasses must implement it to receive messages.
     */
    public void handleMessage(@NonNull Message msg) {
    }

         It can be seen that an empty method handleMessage will be called in the dispatchMessage method, which is exactly the method we need to override when creating the Handler. When does the Handler set it as the target of a Message?

Handler.sendMessage()

         Handler has several overloaded sendMessage methods, but they are basically the same. We use the most common sendMessage method for analysis. The code is as follows:

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

Handler.sendMessageDelayed()

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Handler.sendMessageAtTime()

    public boolean sendMessageAtTime(@NonNull 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);
    }

         After several layers of calls, here we get the MessageQueue created by Looper in the main method of ActivityThread.

         Finally, the enqueueMessage method is invoked to insert Message into the message queue MessageQueue.

Handler.emqueueMessage()

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

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

         Note: set the Handler itself as the target(Handler) object of the Message. Let's take a look at enqueueMessage of MessageQueue   method. Therefore, subsequent messages will call this Handler's dispatchMessage    Method.

MessageQueue.enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        //Note 1, non empty judgment
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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();
            //Note 2, it's important from here down
            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 {
                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;
    }

         Note 1: if msg.target == null is not set, an exception will be thrown directly;

         Note 2: it will be inserted into the MessageQueue in order according to the time when of the Message. It can be seen that the MessageQueue is actually an ordered queue, but it is sorted according to the execution time of the Message.

         The following is the MessageQueue created by Looper in the main method of ActivityThread. After Looper fetches a Message from the MessageQueue, it will call the dispatchMessage method for processing.

         So far, the sending message and message processing flow of the Handler have been introduced.

Focus on

What is the difference between Handler's post(Runnable) and sendMessage(Message msg)

Example:

public class HandlerActivity extends ActivityBase{
    ActivityHandlerBinding binding ;
    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    binding.tvTotle.setText(String.format("Ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha%d",msg.arg1));
                    break;
            }
            return false;
        }
    });
    @Override
    protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityHandlerBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        binding.btnSendMessage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                msg.what=1;
                msg.arg1=100;
                handler.sendMessage(msg);
            }
        });
        binding.btnPost.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        binding.tvTotle.setText(String.format("Ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha%d",80));
                    }
                });
            }
        });
    }
}

Handler.post()

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

         Then call getPostMessage() to Runnable, let's see what this is for.

Handler.getPostMessage()

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

         In this method, you will find that getPostMessage() will assign Runnable to the callback variable of Message and return a Message. And call the sendMessageDelayed method.

Handler.sendMessage()

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

         The comparison shows that they all call sendMessageDelayed(), but the generated messages are different. sendMessageDelayed() has been mentioned above, so I won't describe it more. You can turn it up.

         It can be seen that after several layers of calls, sendMessageDelayed() will eventually call enqueueMessage() method to insert Message into Message queue. This Message queue is the MessageQueue created by Looper in the main method of ActivityThread we just analyzed.

         Loop takes a Message from the MessageQueue through the loop() method, and Message.target(Handrle) will call the dispatchMessage method for processing. Let's take a look at its source code.

Handrle.dispatchMessage()

    /**
     * Handle system messages here.
     * System messages are processed here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        //Note 1
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        //Note 2
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

         Let's look at the handleCallback method first

Handrle.handleCallback()

    private static void handleCallback(Message message) {
        message.callback.run();
    }

Obviously, there are two situations for dispatchMessage:

         Note 1: msg.callback= Null, usually through post(Runnabl), will directly execute the run method of Runnable. Therefore, Runnable here is actually a callback interface, which has nothing to do with Thread.

         Note 2: msg.callback == null, which is generally sendMessage, will call Handler's hanlerMessage method for processing.

Why doesn't loop. Loop () block the main thread

         We just learned that the loop method in loop is actually an endless loop. But our UI thread is not blocked. Instead, it can perform various gesture operations. Why? In the next method of MessageQueue

MessageQueue.next()

    Message next() {
        // mPtr : used by native code
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        
        //-1: Only during the first iteration
        int pendingIdleHandlerCount = -1; 
        //Next Poll timeout
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                //All suspended in the current thread   Binder   Command to refresh to the kernel driver.  
                //This is useful before executing a long time blocking operation.
                //To ensure that any pending object references have been released to prevent the process from holding the object longer than it takes.
                Binder.flushPendingCommands();
            }

            //Note: here comes the point.
            nativePollOnce(ptr, nextPollTimeoutMillis);

            ...
        }
    }

MessageQueue.nativePollOnce()

    /*non-static for callbacks*/
    private native void nativePollOnce(long ptr, int timeoutMillis);

         The nativePollOnce method is a native method. When this native method is called, the main thread will release CPU resources and enter the sleep state until the next message arrives or a transaction occurs. The main thread will be awakened by writing data to the write end of the pipe pipe. The epoll mechanism is adopted here. The following is a partial analysis of nativePollOnce. Please refer to Analysis of nativePollOnce function , those who are interested can go and have a look by themselves. They are a little confused. Look at this when I try again.

Epoll mechanism: it provides the most efficient I/O reuse mechanism on Linux platform. In terms of calling methods, epoll is very similar to select/poll. Its main function is I/O reuse, that is, waiting for I/O events of multiple file handles in one place.
 

         The implementation function of nativepollonce is android_os_MessageQueue_nativePollOnce.

android_os_MessageQueue_nativePollOnce

        frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
     //Take out the NativeMessageQueue object and call its pollOnce
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

pollOnce()

        frameworks/base/core/jni/android_os_MessageQueue.cpp

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    //The task is passed to the pollOnce function of Looper
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

pollOnce()

        system/core/libutils/include/utils/Looper.h

/**
     * Wait for events to become available, optional timeout in milliseconds.
     * Call a callback for all file descriptors where the event occurred.
     * If the timeout is zero, it returns immediately without blocking.
     * If the timeout is negative, wait indefinitely until the event occurs.
     * If you used wake()   Poll is returned when wakeup polling_ WAKE
     * The timeout expires, no callback is called, and no other files exist
     * Descriptor is ready.
     * If one or more callbacks are invoked, the   POLL_CALLBACK. 
     * If there is no data waiting timeout before the given, returns   POLL_TIMEOUT. 
     * If an error occurs during the wait, return   POLL_ERROR. 
     * If the file descriptor has data, a  >=  0   Contains the value of the identifier
     * And it has no callback function (you need the caller to handle it here).
     * In this (and only this) case, outFd, outEvents   and   outData   Will include voting
     * And   fd   Associated events and data, otherwise they will be set to   NULL. 
     *
     * This method does not return until the appropriate callback is called
     * File descriptor for all signaled files.
     */
    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
    inline int pollOnce(int timeoutMillis) {
        //The timeOutMillis parameter is the timeout wait time. If - 1, it means to wait indefinitely until an event occurs. If the value is 0, there is no need to wait for immediate return.
        return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);
    }

pollOnce()

        system/core/libutils/Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //An infinite loop
    for (;;) {
        //mResponses is a Vector, and the response needs to be processed first
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                //First, deal with those responses without callback
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != nullptr) *outFd = fd;
                if (outEvents != nullptr) *outEvents = events;
                if (outData != nullptr) *outData = data;
                //In fact, for a Response without a callback, pollOnce only returns its ident,
                //There was no actual treatment. Because there is no callback, the system does not know how to handle it
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != nullptr) *outFd = 0;
            if (outEvents != nullptr) *outEvents = 0;
            if (outData != nullptr) *outData = nullptr;
            return result;
        }
        //Call the pollInner function. Notice that it's inside the for loop
        result = pollInner(timeoutMillis);
    }
}

How to implement sendMessageDelayed or postDelayed of Handler

         As mentioned above, when inserting messages into the MessageQueue queue, they will be sorted according to the execution time of messages. The core implementation of Message delay processing is in the stage of obtaining Message. Next, let's look at the next method of MessageQueue.

MessageQueue.next()

Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                //  Try to retrieve the next message.   Find and return.
                //Get system time.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        //  The next message is not ready.   Set the timeout to wake up when ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //  Received a message.
                        //mBlocked: indicates   next()   Is it   pollOnce()   Blocking wait with non-zero timeout in.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //return   Message  
                        return msg;
                    }
                } else {
                    //  No more news.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }

         The above indicates that a Message is taken from the MessageQueue, but the current system time is less than Message.when, so it is calculated as a timeout. The purpose is to wake up the UI thread after the timeout period. Therefore, the subsequent code for processing messages will only be executed by the CPU after the timeout period.

Note: it can also be seen from the above code that if the current system time is greater than or equal to Message.when, Message will be returned to Looper.loop(). However, this logic can only ensure that the Message is not processed before when, and can not guarantee that it must be processed at when.

summary

 

 

 

Tags: Android Android Studio

Posted on Sat, 11 Sep 2021 16:17:18 -0400 by bhogg