Android message processing mechanism

Keywords: Android Attribute network Java

_In the process of learning Android, the message mechanism of Android must be mastered. The landlord can only be regarded as a beginner of Android, and he doesn't have a good grasp of Android's message mechanism. Here is a record of my own understanding of Android messaging mechanism, errors, I hope you can correct.
_This article refers to the following articles:
  1.Android asynchronous message processing mechanism lets you deeply understand the relationship between Looper, Handler and Message
  2.Analysis of Android Application Message Processing Mechanism (Looper, Handler)

1. overview

To talk about message processing mechanism, we have to talk about Handler, Looper, MessageQueue, which is a heavyweight figure. Here is an overview of them, and they will be introduced in detail later.
In general, Handler's main function is to send and process messages; Looper's function is to constantly fetch messages from MessageQueue (message queue) and then hand them to Handler for processing; MessageQueue stores Message Sage sent by Handler. Generally speaking, Message is an operation that needs to be performed by the main thread. For example, the text content information we get from the network needs to be displayed on the TextView.

2.Handler

Firstly, we go down from above and start from Handler.
Often, there are basic steps to handle asynchronous messages using Handler. Here's a simple example. Send a String in the thread and let TextView display it.

    private Button mButton = null;
    //Create a Handler object, and notice that we rewrite the handleMessage message
    //The handleMessage method is used to process the messages we send.
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            mButton.setText(msg.obj.toString());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button: {
                sendMessage();
                break;
            }
        }
    }

    private void sendMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //Create a Message Object
                Message message = Message.obtain();
                message.obj = "I was clicked.";
                //send message
                mHandler.sendMessage(message);
            }
        }).start();
    }

In the above code, I believe it's not difficult. Now we need to understand how Handler passes a Message from a sub-thread to the main thread and executes it!

(1).sendMessage method

_We need to understand why, let's send the code of the Message method.

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

_We followed it step by step and finally reached the enqueueMessage method.

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

Among them, the three parameters of this method, queue denotes the message queue that message needs to enter, msg is the Message object we created earlier, uptimeMillis denotes the delay time. Delay time is not important here. Let's not look at it first.
We can see from the source code that first msg.target = this. This sentence assigns a handler to the target attribute of the message. This step is very important and needs to be used later. Let's first remember that this means which handler this message belongs to, because there may be a lot of handlers sending messages!
mAsynchronous We don't look, it doesn't matter here. Finally, we call the queue.enqueueMessage(msg, uptimeMillis) method, which literally means adding a message to queue's message queue.
Let's not look at the code in queue first. Let's first summarize the information we've got.

We send a Message in a sub-thread and eventually enter a queue.
_2. Before Message enters the message queue, the target field is used to mark which Handler is sending the current message.

Here, we seem to have no harvest, no hurry, let's take a look at the creation of those things for us when we construct a Handler!

(2). The Construction Method of Handler

_Handler's construction method is finally called into the Handler(Callback callback, boolean async) method.

    public Handler(Callback callback, boolean async) {
        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());
            }
        }

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

_In the construction method of Handler, we find several small details, mLooper, mQueue are assigned. Looper, as we'll see later, when we call the sendMessage method, we find that it calls this step:

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

_What we noticed was that the queue in the previous enqueueMessage was mQueue.
Here we know:

When a Handler is created, it creates a MessageQueue and a Looper, that is, a Handler binds a MessageQueue and a Looper at the same time.
When we call sendMessage and other methods to send messages, the message queue that the message enters is Handler's own MessageQueue.

2.Looper

I remember in the overview, Handler is to send and process messages. Now we have a simple understanding of sending messages, but we have no introduction to processing messages. Don't worry, if you want to know how to process the message, we must first know how to get the message. Is it Handler who reaches directly for it from MessageQueue? How could it be so simple? Next, let's introduce Looper, another heavyweight figure.

(1).Looper Construction Method

Let's first look at Looper's construction method.

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

_We found in this constructor that Looper creates a MessageQueue. But apart from this information, there seems to be no other useful information.
What should I do? Usually, Looper is not created directly. From here we can see that its construction method is private, but calls the prepare r method.

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

In the prepare dness method, it should be noted that the sThreadLocal variable is a ThreadLocal variable. What are the variables of this type? Specific explanations can be found online (actually I don't understand, haha!!!). Here's a simple explanation. ThreadLocal encapsulates a variable to store the corresponding information. The key is that different threads get different variables from ThreadLocal. Each thread gets its own variables from ThreadLocal, and threads do not interact with each other.
From here, we can see that each thread has its own Looper object.
Here we know:

Looper objects cannot be directly new, but are created by calling the static method prepare method
_2. The same thread cannot call the prepare method twice because the Looper object is stored in a ThreadLocal object.
_3. From the previous Handler, we can see that there is a Looper object in Handler (by calling method mLooper = Looper.myLooper()). This object is the TreadLocal encapsulated object. Thus, if this handler is created in the main thread, then Looper must belong to the main thread.

From the above conclusion, we can conclude that if we create a Handler in a thread, we must first create the Loper object of the current thread. But, as you can see, in the previous example, we created a Handler object directly in an Activity without creating a Loper for the main thread. Why is there no error? We can assume that when we create Handler, the main thread's Looper is already prepare d! But where exactly is it called? We'll show it later!

(2) loop method

As I said before, Looper's role is to constantly extract messages from MessageQueue (Message queue) and then hand them to Handler for processing. But we still don't know how Looper actually gets messages from MessageQueue, which leads to our loop method.
Let's first look at the source code of the loop method. Instead of posting the complete code, we deleted some code that we thought was unimportant:

    public static void loop() {
         //Get the Loper of the current thread -- remember the ThreadLocal variable we mentioned earlier, which holds the current thread's
         //Looper object
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //Get the current Looper message queue object
        final MessageQueue queue = me.mQueue;
       // Dead cycle
        for (;;) {
            //If there is no message, the current thread will be blocked.
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //Distributing the current message to Message's own Handler
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            msg.recycleUnchecked();
        }
    }

_Let's sort it out first. This method mainly describes the content:

_1. First, get the Loper object and the MesageQueue object of the current thread
_2. Get Message from MessageQueue; if get Message, distribute it to Message's own Handler for processing; conversely, if not, thread will be blocked in next method.
_3. The acquired message is passed from MessageQueue to Handler through Handler's dispatch Message method, which is handled by Handler.

Here, let's sort out our ideas. First, a handler is created in the main thread to indicate that the current Looper is placed in the main thread, and of course MessageQueue belongs to the main thread. It should be noted here that a thread can only have one Looper, because MessageQueue is in Looper, so there is only one MessageQueue, but a handler in a thread can be created many times, so the same MessageQueue and Looper are held in the same thread's Handler.
_We use Hadler created in the main thread to send messages by calling sendMessage and other methods, and the final message queue is Message Queue of the main thread. We get the message in Looper's loop method, and then distribute it to the target of the message, which is the Hadler that sends the message, for processing.
This explains why we send a Message in a sub-thread and receive it in the main thread's Handler to process the Message. Because the handler itself belongs to the main thread, including the Looper and MesageQueue held in the handler belong to the main thread.
_Here is a question, that is, we know that when we create Handler in the main thread, in fact, the main thread's Looper has already been created, how can we see that?
For example:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //Here we annotate Looper's prepare dness method
                //Represents that the current thread Looper is not initialized
//                Looper.prepare();
                Handler mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", msg.obj.toString() + " " + Thread.currentThread().getName());
                    }
                };
                Message message = Message.obtain();
                message.obj = "pby123";
                mHandler.sendMessage(message);
            }
        }).start();

Then let's run the program and find a notorious anomaly!

_We should not be unfamiliar with this exception, because wherever we want to use Looper, we should check whether Looper is pre-completed.
In the example above, we created Handler from a new thread. Because it is a new thread, we must create the Looper and MessageQueue of this thread.
_Thus, we can create Handler directly in the main thread, indicating that the main thread's Looper and MesageQueue were created before we created Handler!
_Where exactly is the Loper for the main thread created? This leads to the main method of the ActivityThread class. We all know that the main method is the entrance to Java programs. Let's look at the main method:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

_ Me too. After seeing the main method, I feel that many problems have been solved before.
In the main method, we see Looper.prepareMainLooper(); this sentence means the Looper of the initialized main thread. From here, we know why we can create Handler in the main thread instead of preparing Looper.
_At the same time, we also note that Looper.loop(). As we have analyzed before, the loop method is mainly used to cancel the message in Message Queue. Because this is the Loper of the main thread, the loop method here is to fetch the message from the Message Queue of the main thread.
Before, we used a Hadler created in the main thread to send messages in a sub-thread, and finally to Message sageQueue in the main thread. But when is the loop called? It's actually called in the main method.
In the same way, if we create Handler, Looper and MessageQueue in a new thread, when we call Handler's sendMessage method to send a Message, handleMessage method in Handler will not receive this Message object, because Looper's loop method starts, the current thread.
We know that the loop method is a dead loop, but why not lead to the application of ANR? In fact, there are many explanations on the Internet. Maybe they are too stupid to understand those ideas. In the future, we will explain the reasons by combining Looper's loop method with some methods of MessageQueue.

4.MessageQueue

We know that Message Queue is equivalent to a container for storing messages. Handler puts messages into this container and Looper takes data from it. Let's take a look at the data storage section first.
There is a sentence in Looper's loop method:

Message msg = queue.next(); // might block

_The above sentence is the operation of Looper to fetch messages from Message Queue. Let's pay attention to the official annotations. This sentence may lead to blocking!
_Then under what circumstances will it be blocked? Will the current MessageQueue be blocked when it is empty? If MessageQueue is empty, it will be blocked. What is the effect of this sentence?

            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

(1) Next method

_Let's look at the next method first. Here I post all the code of next, and then I analyze part of it.

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        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 msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

First of all, let's take a look at this sentence.

            nativePollOnce(ptr, nextPollTimeoutMillis);

On the basis of Analysis of Android Application Message Processing Mechanism (Looper, Handler) NativePollOne is used to indicate that the current thread will be blocked. NextPoll Timeout Millis means how long the current thread will need to be blocked. If nextPoll Timeout Millis is 0, it means no blocking; - 1 means unlimited blocking unless a thread wakes it up; other positive values indicate how long the blocking lasts.
Then let's look at the following code

                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        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 msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

Among them, msg represents the first available Message in the current message queue. It first determines the size of the current time and the execution time of the message. If the current time is less than the execution time of the message, that is to say, the execution time of the message has not yet arrived, it assigns a value to nextPoll Timeout Millis to indicate how long the thread will block.
However, if the current time corresponds to the execution time of the Message, it must immediately return to the Message to perform the operation inside.
If the first Message currently obtained is empty, it means that there is no Message in the current MessageQueue, so reset nextPoll Timeout Mills to - 1, indicating that the current will be blocked indefinitely unless other threads will wake up!
Among them, we also need to pay attention to:

                if (mQuitting) {
                    dispose();
                    return null;
                }

_This return is the second return null in the whole next method. Do you remember a piece of code in the loop method? That is, if the method you get from the next method is null, you exit the loop directly. The return condition of this return null can explain our previous question --- if MessageQueue is empty, it will be blocked, what is the effect of this sentence?
The first condition of return null is that mPtr equals 0. I am not clear about what mPtr equals 0 means. Maybe I should say the address of the current MessageQueue counterpart. This is just my guess that is not necessarily correct.
Let's look at the condition of the second return null, from which we can see that only when mQuiting is true can we return null. From the variable name, we can see that when the MessageQueue is quit, it returns null. That is, when MessageQueue is to be recycled, Looper exits its loop dead loop. In other cases, loops are in a dead loop.
_However, in which case will MessageQueue be recycled? We found that MessageQueue's quit method is the default modifier, that is, it can only be called under the same package! In our application code, we can't call it directly. Where can we call the quit method? We found the answer in Looper's quit method:

    public void quit() {
        mQueue.quit(false);
    }

We know that each Looper object is placed in a ThreadLocal variable, that is to say, which thread calls Looper's quit method, which thread's MesageQueue quits the Looper loop.
For example:

    private void sendMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", (msg.obj) + "");
                    }
                };
                Message message = Message.obtain();
                message.obj = "I am Message";

                handler.sendMessageDelayed(message, 2000);
                Looper.loop();
                Log.i("pby123", "123");


            }
        }).start();
    }

In the above code, we create a Handler and a Looper in a new thread, then use the Handler to send a message, and finally call Looper's loop method to make the Looper live! Then let's look at log:

_We will find that 123 log has not been printed at all, that is to say, it has not been executed at all, so we can infer that Looper's loop method is still executing at this time, of course, if there is no message, it should be blocked. This phenomenon indirectly proves that we understand the source code!
It should be noted here that in the same thread, the code cannot be written like this:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //Here we annotate Looper's prepare dness method
                //Represents that the current thread Looper is not initialized
                Looper.prepare();
                Handler mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", (msg.obj) + "");
                    }
                };

                Looper.loop();

                Message message = Message.obtain();
                message.obj = "I am Message";

                mHandler.sendMessageDelayed(message, 2000);
                Log.i("pby123", "123");


            }
        }).start();

_Written like this, loop has been executed, can not execute to loop code at all!
_thus knows:

_1. Except for the main thread, the loop method of Looper in all threads will not be invoked automatically, although we call the relevant method of sending messages by Handler. It is only when we explicitly call the loop method in the thread that we loop.
_2. The loop method is a dead loop, which constantly fetches messages from MessageQueue. Only when the extracted Message is null can the loop's dead loop be terminated.
In the next method of Message Queue, if the execution time of the extracted message is longer than the current time, the current thread will be blocked by the corresponding difference time. But if the current MessageQueue is empty, the current thread will be blocked indefinitely.
If the current MessageQueue is called quit method, Looper's loop method takes null out of MessageQueue, which results in Looper's loop method execution.
_4.MessageQueue's quit method can not be directly invoked, but indirectly implemented through Handler's quit!

Here we can see why the loop method does not lead to ANR. This is because loop is not always executed, but only when there is a Message in the current MessageQueue, it will constantly get the Message; if the MessageQueue is empty, the thread will be blocked, and only when the Message is awakened, the loop method will be alive again!

(2) EnqueueMessage method

To be honest, the next method is really not very well understood, especially after combining Handler and Looper! However, we finally have a general understanding of the next, I dare not say in-depth understanding, haha! Because I am a vegetable chicken!
With a general understanding of the next method, it shouldn't be very difficult to understand the enqueueMessage method. Let's first look at the code for the enqueueMessage method:

    boolean enqueueMessage(Message msg, long when) {
        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;
    }

_In the code above, I deleted some of the code that I thought was unimportant. This method is very understandable. First, determine whether the MessageQueue is marked as a recycle. If the Message is recovered, false is returned, indicating that the insertion of a new Message failed; secondly, if it is not recovered, the insertion inserts the Message into the correct location, which means that the Message is sorted from small to large according to the execution time of the Message, and the minimum time is placed at the head of the team!
I think the most important step here is this:

            if (needWake) {
                nativeWake(mPtr);
            }

If you need to wake up now, wake up!

5. summary

To tell you the truth, this article is almost finished now. It's time to write a summary and summarize what you understand.

_1. Before creating a Handler, the Looper thread of the current thread must be created, that is, Looper's preapre method must be placed before the Handler is created. The reason why we don't need to create Looper in the thread is that Google Dad has created Looper for us in ActivityThread's main method!
Looper will not be called automatically, it must be called in the last line of code in this thread! Because once the loop method has been executed, it means that the current thread is about to be executed. This can refer to ActivityThread's main method code!
_3. When calling Handler's method of sending a message, the message will eventually be put into the MessageQueue bound to the thread. In putting in Message, it is important to note that Message itself carries the when attribute, which represents the time of the message, and the order ordered by the When used within MessageQueue.
In Looper's loop method, next of MessageQueue is constantly called to get a message. When getting a message, there are two situations: 1.Message is not null, then the message is distributed to the Handler where the message belongs to for consumption; 2. If null, it means that the current MessageQueue has been called quit method, the loop method will end, and the whole thread will end!
5. In the next method of Message Queue, if the execution time of the acquired Message is longer than the current time, it means that the execution time of the Message has not been reached, so the current thread blocks the corresponding time; if the acquired Message meets the execution time, it immediately returns; if the current Message Queue is empty, immediately block the current thread!

Finally, in combination with the knowledge to explain why we send a Message in a sub-thread, the main thread will be accepted.
Firstly, we use a handler created in the main thread to send messages. Although it is said to send messages in the sub-thread, Message has a target to record the handler that sent it. At the same time, when the handler is created, it holds the current thread's Looper and MesageQueue, so the message is eventually sent to the main thread's MesageQueue. In ActivityThread's main method, the main thread's Looper is constantly loop ing, so it constantly takes messages from its own thread's MesageQueue to consume. After the message is retrieved, it is given to target's handler Message method to consume. This target is the Andler created by the main thread itself. Ultimately, we can see why the Message sent in the sub-thread can reach the Handler in the main thread.

Posted by AnarKy on Sat, 18 May 2019 12:18:38 -0700