Android message processing mechanism (Looper, Handler, MessageQueue,Message)

Keywords: Android Java Google Linux

Android message processing mechanism is estimated to be rotten, but still need to be written, because Android applications are driven by messages, Android can be said to be a message-driven system in a sense, UI, events, life cycle are closely related to message processing mechanism, and message processing mechanism is particularly important in the entire Android knowledge system. In too many articles about source code analysis, many people are still confused about the whole message processing mechanism. This article explains the working principle of Android main thread (UI thread) through some question-and-answer modes. Source code annotations are complete and flow charts are combined. If you don't understand Android message processing mechanism very well, I believe that as long as you quiet down. Look with patience, there will certainly be a lot of gains.

Summary

1. Let's start with what is Android message processing mechanism?

The essence of message processing mechanism: a thread opens loop mode to continuously listen and process messages sent to it by other threads in turn.

Simply put: a thread opens an infinite loop mode and traverses its message list continuously. If there is no message in the list, it will block itself (equivalent to wait ing, giving up cpu resources to other threads). If other threads want to do something about the thread, they will insert messages into the message queue of that thread. The thread will continue to insert messages from the message queue of that thread. The queue takes out the message for processing.

2. How does Android message processing work?

For example, company analogy App

  • The main work of PM is to design products, write requirements documents, change requirements, change requirements halfway, and test pre-change requirements.
  • The main work of UI is UI design, interaction and so on.
  • I won't talk about RD work.
  • The CEO doesn't explain.

After the company started (App started), then the CEO started to work (main thread [UI thread] started), when the CEO opened the infinite cycle workaholic mode, his company could not (equivalent to UI main thread turned into Looper thread [source code contains] CEO recruited an RD (new). Handler instance) and tell PM and UI that if you have any tasks and requirements, let RD (Handler instance) pass it on to me (CEO). RD records PM and UI requirements (Messages) in the CEO's memo (MessageQueue). CEO The work of the infinite cycle is to keep looking at the memo and see what tasks to do. When there are tasks, take them out one by one from the memo and hand them to the RD (Handler). Instances) to deal with (after all, CEOs don't write code...). Of course, if all the memos are finished, then the CEO will go to sleep (thread blockage [simply understood as thread wait], leaving CPU resources for other threads to execute). But this memo has a special function: when there is no task, suddenly insert the first task (from scratch) and the alarm clock will wake up the CEO to continue processing the memo. This is how the whole message processing mechanism works. Source code analysis will follow, and you can better understand this scenario.

Here we first give a flow chart of Android message processing mechanism and specific execution animation. If you don't understand it well, then look down (which will be explained by the main thread of Android UI later), and then look at the implementation principle of the whole mechanism in combination with graphics and animation.



3. Looper, Handler, MessageQueue, the role and significance of Message?

  • Looper 
    We know that a thread is an executable code. When the executable code is executed, the thread life cycle will terminate and the thread will exit. What happens if the code segment is executed as the main thread of App? It's unreasonable that App automatically exits after it starts executing a piece of code. So in order to prevent the code segment from being executed, only a dead loop can be inserted into the code, then the code will not be executed, and then automatically exit. How to insert a dead loop into the code? Then Looper appears. Calling Looper.prepare()...Looper.loop() in the main thread will change the current thread into a Looper thread (which can be easily understood as an infinite loop does not exit thread). There is a dead loop code in the Looper.loop() method, so the main thread will enter the while(true) {...} code segment and can't jump out, but the main thread can't do anything? In fact, everything is done in while(true) {...}. The main thread will wait for other threads to send messages to it in the dead cycle (including: Activity start, life cycle, update UI, control events, etc.). Once there is a message, it will be processed accordingly. Another part of Looper's work is to remove messages from the message queue one by one in the loop code. Information is processed by the main thread.

  • MessageQueue 
    MessageQueue exists simply because the same thread can only process one message at the same time, and the code execution of the same thread is not concurrent, so queues are needed to save messages and arrange the processing order of each message. Many other threads send messages to the UI thread. The UI thread must keep these messages in a list (it can't handle so many tasks at the same time), and then take them out one by one. This design is very simple. We usually write code like this. Each Looper thread maintains such a queue, and only this one, the message of the queue can only be processed by that thread.

  • Handler 
    Simply put, Handler is used to communicate between threads of the same process. Looper lets the main thread take out message processing from its own Mesage Queue indefinitely and circularly. Since we know that processing messages must be processed in the main thread, how can we put messages into the queue of the main thread from other threads? In fact, it is very simple. We know that resources are shared between threads and threads in the same process. That is to say, any variable can be accessed and modified in any thread. As long as concurrency is considered and synchronization is done, we can get MessageQueue. For instance, you can put a message into the Massage Queue of the main thread, which processes the message in the main thread when polling. So how to get an instance of MessageQueue in the main thread is available (mLooper under the main thread) = Looper.myLooper();mQueue = mLooper.mQueue;), but in order to unify the callback processing of adding messages and messages, Google specially constructs Handler classes. You only need to construct Handler classes in the main thread, then the Handler instance gets the reference of the main thread MessageQueue instance (access method mLooper.mQueue;). = Looper.myLooper();mQueue = mLooper.mQueue;) Handler inserts new messages into the message queue through this reference when sendMessage. Another function of Handler is to handle callbacks of messages uniformly. Such a handler sends messages and ensures that message processing is done by itself. This design is very commendable. The idea is that the message in the queue holds a reference to the handler (which handler puts it in the queue, the message holds the reference to the handler), and then when the main thread polls the message, it calls back and forth the handleMessage(Message) of the handler that we often rewrite. MSG method.

  • Message 
    Message is very simple, you want to let the main thread do anything, always tell it, always pass point data to it, Message is the carrier.

Source code analysis

Next, we will combine the main thread of App (UI thread) to explain the whole Android message processing mechanism step by step from the start of App. First, we have a familiar main function in the ActivityThread class. Here is the entry of the code started by App. The UI thread is only a common thread. Here it will convert the UI thread into Looper thread. What is the Looper thread? Cheng, just look down.

public final class ActivityThread {
    public static final void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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

The first thing to do is Looper.prepareMainLooper(). Let's see what this method in Looper does.

Note: See what ThreadLocal is before you know it a little bit?
ThreadLocal: Thread Local Storage (TLS), where each thread has its own private local storage area and different threads cannot access each other's TLS area. Here the thread's own local storage area is the thread's own Looper. Look at the source code of ThreadLocal.java!

public final class Looper {
    // sThreadLocal is a variable of static, which can be understood as map, key as thread and value as Looper.
    //Then you can get the Loper of the current thread through sThreadLocal as long as you use the current thread.
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    //The Looper of the main thread (UI thread) is handled separately, and is of static type, by getMainLooper() 
    //It is easy to get the Loper of the main thread.
    private static Looper sMainLooper; 

    //Message queues for threads to which Looper belongs
    final MessageQueue mQueue;
    //Looper's thread
    final Thread mThread;

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
         //If the TLS of a thread already has data, an exception will be thrown. A thread can only have one Looper, and prepare s cannot be called repeatedly.
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //Inserting data into TLS of threads is a simple understanding equivalent to map. put (Thread. current Thread (), new Looper (quitAllowed)).
        sThreadLocal.set(new Looper(quitAllowed));
    }

    //Actually, you call prepare(false) and assign a value to sMainLooper.
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    //static method, easy to get the main thread of the Loper.
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    public static @Nullable Looper myLooper() {
        //Look specifically at the source code get method of the ThreadLocal class.
        //Simple understanding is equivalent to map.get(Thread.currentThread()) getting the Looper of the current thread
        return sThreadLocal.get();
    }
}

Looking at the above code (take a closer look at the comment), we find that the event Looper.prepareMainLooper() does is new a Looper instance and put it into ThreadLocal < Looper > of a static under the Looper class. In sThreadLocal static variable, sMainLooper is assigned to sMainLooper at the same time. The purpose of sMainLooper assignment is to get the Looper of the main thread quickly through Looper.getMainLooper(). sMainLooper, which is the Looper of the main thread, may be acquired more frequently to avoid arriving every time. sThreadLocal to find and get.

The next point is to look at Looper's constructor and see what you did with the new Looper.

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

See no, at this point a message queue MessageQueue is created for the current thread, and Looper holds a reference to the MessageQueue. After Looper.prepareMainLooper() is executed, the main thread changes from a normal thread to a Looper thread. The current main thread thread thread already has a Looper object and a message queue mQueue. The reference relationship is as follows: (the main thread can easily get its Looper, and the main thread's Looper holds the reference of the main thread's message queue)



How to get the main thread's Looper object and message list?

//Execute in the main thread
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue
//perhaps
mLooper=Looper.getMainLooper()

Next, go back to the main function of ActivityThread, and after executing Looper.prepareMainLooper(), the next line of code is ActivityThread. thread = new ActivityThread(); This sentence is to create an ActivityThread object, which is not a thread, it does not inherit Thread, but is just a common class public. The constructor of final class ActivityThread{...}ActivityThread does nothing but initializes the resource manager.

 ActivityThread() {
     mResourcesManager = ResourcesManager.getInstance();
 }

Next, look at the next line of code

ActivityThread thread = new ActivityThread();
//Create Binder Channels (Create New Threads)
thread.attach(false);

thread.attach(false); creates a Binder thread (specifically, Application Thread, which sends messages to the main thread by thinking of Handler, and then talks about it). We mentioned earlier that the main thread will eventually enter an infinite loop. If no other thread is created before entering the dead loop, who will send a Message to the main thread later? That's where the thread is created. It receives events from system services that encapsulate messages and send them to the main thread, which takes them from the queue and processes them in an infinite loop. (Messages from Binder threads include LAUNCH_ACTIVITY, PAUSE_ACTIVITY, etc.)

Let's go back to the next sentence of the mian function, Looper.loop(), so let's look at the source code of Looper.loop():

public static void loop() {
    final Looper me = myLooper();  //Get the Looper object stored in TLS and the Looper of the current thread 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    final MessageQueue queue = me.mQueue;  //Get the message queue in the Looper object
    ....

    for (;;) { //Main thread opens infinite loop mode
        Message msg = queue.next(); //Getting the next message in the queue may cause thread blocking
        if (msg == null) { //If there is no message, exit the loop, exit the message loop, and your program can exit.
            return;
        }
        ....
        //Distributing Message, msg.target is a Handler object, which Handler sends this message to the queue,
        //The Message holds the reference to the Handler and puts it in its target variable so that we can call back and rewrite it.
        //handleMessage method of handler.
        msg.target.dispatchMessage(msg);
        ....
        ....
        msg.recycleUnchecked();  //Recycle the Message to the Message pool. Next time you need to use it, you don't need to recreate it. Just get ().
    }
}

The above code, you look at the comments specifically, when the main thread (UI thread) execution of this step into a dead cycle, constantly to get the message queue out of the message processing? So here comes the question...
1. UI threads have been unable to jump out of this loop. Does the main thread not get stuck because of the dead loop in Looper.loop(), so how can it perform other operations?

  • After looper starts, any code executed on the main thread is taken out of the message queue by looper to execute. That is to say, after the main thread, other threads send messages to it to perform other operations. The same is true for life cycle callbacks. The system service Activity Manager Service sends IPC calls to the APP process through Binder. When the App process receives the call, it inserts a message into the message queue of the main thread through the Binder thread of the App process.

2. The main thread is the thread that UI thread interacts with user. The priority should be high. Does the dead cycle of the main thread run all the time consume CPU resources? What about the other threads of the App process?

  • This is basically a producer-consumer model. Simply put, if there is no message in the main thread's Message Queue, it will block in the queue.next() method of loop. At this time, the main thread will release CPU resources into dormant state, until the next message comes in, it will wake up the main thread. Before version 2.2, this mechanism used the familiar thread's wait and noti. The later version involves the Linux pipe/epoll mechanism, which wakes up the main thread by writing data to the pipe pipe writer. The principle is similar to I/O. Reading and writing are blocked and do not occupy CPU resources.

So the focus of the above code is on the queue.next() function. Let's not go into the rest. Let's look at the source code of queue.next() (mainly the comments):

Message next() 

        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration

        //NextPoll Timeout Millis indicates that the nativePollOne method needs to wait for NextPoll Timeout Millis 
        //Will return
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //Read the message, there is no message in the queue may be blocked, the method will return in two cases (code can be executed down)
            //One is to wait until a message is generated and return.
            //Another is that nativePollOne will return after waiting for NextPoll Timeout Millis for a long time.
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //NativePollOne will not be executed until it returns
            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) {
                    // The loop finds a message that is not asynchronous and msg.target is not empty
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                       // There's news, but it's not time to run, like postDelay, which we often use.
                       //Calculate how long before execution time is assigned to nextPoll Timeout Millis,
                       //Represents that the nativePollOne method waits for nextPollTimeoutMillis to return after a long time
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Get the message
                        mBlocked = false;
                       //List some operations, get msg and delete the node 
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //Return to the message
                        return msg;
                    }
                } else {
                    //No message, nextPoll Timeout Millis reset
                    nextPollTimeoutMillis = -1;
                }
                .....
                .....

    }

nativePollOnce() is an important function of natives. It has done a lot of work in natives. It mainly involves the processing of epoll mechanism (blocking in the reading end of pipeline when there is no message processing). This article does not deal with the source code related to natives. Interested students can find it online, and many of them have deep analysis.

From application startup to Looper creation, message queue creation, to infinite loop execution, then this piece is over. The main thread has polled and waited for messages in the dead loop. Next, we need to see how the system sends messages to the main thread. How does the main thread process these messages?

When preparing to start an activity, the Activity Manager Service (AMS) thread under the system service process sends IPC calls to the APP process through Binder. When the App process receives the call, the Binder thread under the App process finally calls the scheduleLaunchActivity method under the ActivityThread class to prepare to start the activity. Look at the scheduleLaunchActivity method:

Note: Binder Thread: Specifically refers to the thread that receives information from the system process in the App process (which is created before the main thread enters the dead cycle).

  //This method is not called by the main thread, but by the Binder thread.
  public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
  }

After encapsulating the start information into Activity Client Record, the last sentence calls sendMessage(H.LAUNCH_ACTIVITY, r) Let's look down:

private void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
         Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

See no, the last information to start Activity encapsulates a Message, but there is a problem. When we analyzed the main function, we did not give the way to insert messages into the Message queue of the main thread at all. There are messages here, but how to send messages to the Message queue of the main thread? The last sentence is the key mH.sendMessage(msg); mH What is it? Is it Handler? Let's see what it is.
We looked at the member variables of ActivityThread and found an initialization code

final H mH = new H();

Continue to see what H is?

public final class ActivityThread{
     ....
     final H mH = new H();
     ....
     private class H extends Handler {
     ....
     ....
     public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                   .....
         }
         .....
         .....
     }
}

H is not only Handler, but also the inner class of ActivityThread. Look at its handleMessage method, LAUNCH_ACTIVITY, PAUSE_ACTIVITY, RESUME_ACTIVITY... a lot more. Classes help us deal with many declaration cycles. Then go back to the code mH.sendMessage(msg), and execute mH.sendMessage(msg) on the Binder thread; the Andler created by the main thread The mH instance sends messages to the message queue of the main thread. The message queue starts from scratch, the blockage of the main thread is awakened, the main thread loop gets the message, and calls back the handleMessage method of mH to process LAUNCH_ACTIVITY. Wait for news. In order to achieve the start of Activity.

Speaking of this, you may want to know more about mH.sendMessage(msg), and how Hanlder sends messages to the message queue of the main thread. Let's talk about Handler next, first look at the source code of Handler! Let's first look at the parametric constructor of Handler, which we often use. The actual call is Handler(Callback). Callback, Boolean async constructor (see comment)

 public Handler() {
        this(null, false);
 }
 public Handler(Callback callback, boolean async) {
        //It's not static that warns of possible memory leaks!
        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());
            }
        }
        //Get the Looper of the current thread, remember the Looper.myLooper() method mentioned earlier?
        //The internal implementation of Looper.myLooper() can be simply understood as map. get (Thread. current Thread ()) 
        //Get the Loper of the current thread
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            //The current thread is not a Looper thread and does not call Looper.prepare() to create a Looper object for the thread.
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //Let Handler hold a reference to the current thread message queue
        mQueue = mLooper.mQueue;
        //These callbacks, regardless, are mainly used for callbacks to handler messages, which have higher priority than handler messages, but are not commonly used.
        mCallback = callback;
        mAsynchronous = async;
    }

The above code illustrates the following questions:
1. Under which thread the Handler object is constructed (under which thread the constructor of the Handler is called), the Handler will hold the Loper reference of the thread and the reference of the message queue of the thread. Because the reference to the message queue holding the thread means that the handler object can add messages to the message queue of the thread in any other thread, and that handler message of the handler must also be executed in that thread.
2. If the thread is not a Looper thread, the new Handler will report an error in this thread!
3. The above two points synthesize the following common code: converting ordinary threads to Looper threads, why create Handler between Looper.prepare() and Looper.loop():

 class LooperThread extends Thread {
       //Other threads can add messages to the message queue referenced to the thread through mHandler
       public Handler mHandler;
       public void run() {
            Looper.prepare();
            //You need to create a Handler instance for external threads to send messages to themselves before the thread enters the dead loop.
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //Handler objects are built on this thread, and the handleMessage method is executed on that thread
                }
            };
            Looper.loop();
        }
    }

Next, let's look at Handler's sendMessage(msg) method, which is also important and commonly used. There are many sendXXXX XX start methods sendMessageAtTime, sendEmptyMessageDelayed, sendEmptyMessage and so on, which are used to add messages to the message queue, then these methods will eventually call enqueueMessage to achieve message entry into the queue:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //It's important to have the message hold a reference to the current Handler when the message is polled by the Looper thread.
        //Callback handleMessage method of handler
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //Call the enqueueMessage method of MessageQueue to queue messages
        return queue.MessageQueue(msg, uptimeMillis);
    }

Let's look at the enqueueMessage(msg, uptimeMillis) method of MessageQueue:

    boolean enqueueMessage(Message msg, long when) {
        // msg must have target, that is, handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //Synchronization is required when inserting a message queue, because multiple threads will insert messages to the queue at the same time.
        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();
            //when represents the execution time of the message, the queues are sorted by the execution time of the message
            //If handler calls postDelay, when=SystemClock.uptimeMillis()+delayMillis
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // p==null indicates that there is no message in the current message queue
                msg.next = p;
                mMessages = msg;
                //You need to wake up the main thread. If there are no elements in the queue, the main thread will block the reader of the pipeline.
                //When the waiting queue suddenly gets a message, it writes characters to the pipe and wakes up the main thread.
                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;
                //Put the message in the exact position of the queue. The queue is sorted according to when of msg. The list is operated by itself.
                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;
            }

            // If we need to wake up Looper threads, we're not going to go into it in depth by calling native to implement epoll wake-up threads.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Finally, let's look at Handler's dispatchMessage method, which is called through msg.target.dispatchMessage(msg) when the Looper thread comes out of the message queue.

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        //callback method is called first
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //Finally, we call back the handleMessage method we overridden
            handleMessage(msg);
        }
    }

At this point, the Java layer content of Android's message processing mechanism is basically explained. (Welcome to my brief book: Kelin)

Posted by cihan on Sun, 23 Dec 2018 23:18:06 -0800