Android message distribution Handler must know

1.Handler message model diagram

Key classes included:

MessageQueue, Handler and Looper, and Message

  • Message: the message to be delivered, which can deliver data;
  • MessageQueue: message queue, but its internal implementation is not a queue. In fact, it maintains the message list through a single linked list data structure, because the single linked list has advantages in insertion and deletion. The main functions are to deliver messages to the message pool (MessageQueue.enqueueMessage) and take messages from the message pool (MessageQueue.next);
  • Handler: Message auxiliary class, which is mainly used to send various message events (Handler.sendMessage) to the message pool and process corresponding message events (Handler.handleMessage);
  • Loop: continuously execute loop (loop. Loop), read the message from the MessageQueue, and distribute the message to the target handler according to the distribution mechanism.

Main relationships among the three:

Only one Looper can exist in each thread. The Looper is saved in ThreadLocal. The main thread (UI thread) has automatically created a Looper in the main method. In other threads, you need to manually create a Looper. Each thread can have multiple handlers, that is, a Looper can process messages from multiple handlers. A MessageQueue is maintained in Looper to maintain the Message queue. Messages in the Message queue can come from different handlers.

2.Must know topic

Handler design philosophy

1. The meaning of the existence of handler, why is it designed like this, and what is the use?

The main function of Handler is to switch threads. It manages all message events related to the interface.

To sum up: Hanlder exists to solve the problem that the UI cannot be accessed in the child thread.

2. Why can't I update the UI in the child thread? Can't I update it?

UI control access in Android is non thread safe.

What if you lock it?

  • It will reduce the efficiency of UI access: the UI control itself is a component close to the user. After locking, it will naturally block, so the efficiency of UI access will be reduced. Finally, the user end is that the mobile phone is a little stuck

Android has designed a single thread model to handle UI operations, coupled with Handler, which is a more appropriate solution.

3. How to solve the cause of UI crash caused by sub thread update?

The crash occurred in the checkThread method of the ViewRootImpl class:

void checkThread() {
   if (mThread != Thread.currentThread()) {
       throw new CalledFromWrongThreadException(
           "Only the original thread that created a view hierarchy can touch its views.");
   }
}  

In fact, it determines whether the current thread is the thread when ViewRootImpl is created. If not, it will crash. The ViewRootImpl is created when the interface is drawn, that is, after onResume. Therefore, if the UI is updated in the child thread, it will be found that the current thread (child thread) and the thread created by View (main thread) are not the same thread and crash.

terms of settlement:

  • Update the UI of the View in the thread that creates the new View. The main thread creates the View, and the main thread updates the View.
  • Before the ViewRootImpl is created, update the UI of the child thread. For example, update the UI of the child thread in the onCreate method.
  • The child thread switches to the main thread to update the UI, such as the Handler and view.post methods (recommended).

MessageQueue related:

1. The function and data structure of messagequeue?

Android uses a linked list to implement this queue, which also facilitates the insertion and deletion of data. No matter which method sends the message, it will go to sendMessageDelayed

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

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
   MessageQueue queue = mQueue;
   return enqueueMessage(queue, msg, uptimeMillis);
}

Process of message insertion:

First, set the when field of the Message, which represents the processing time of the Message, and then judge whether the current queue is empty, whether it is an instant Message, and whether the execution time when is greater than the Message time in the header. If any one is satisfied, insert the current Message msg into the header.
Otherwise, you need to traverse the queue, that is, the linked list, find out when is less than a node, and insert it after finding it. Inserting a message is to find the appropriate location to insert the linked list through the execution time of the message, that is, the when field. The specific method is to use the fast and slow pointers p and prev through an endless loop and move backward one grid at a time until we find that the when of a node p is greater than the when field of the message we want to insert, then insert it between p and prev. Or traverse to the end of the linked list and insert it to the end of the linked list.

Therefore, MessageQueue is a special queue structure used to store messages and implemented with a linked list.

2. How is delayed message implemented?

Whether it is an instant message or a delayed message, the specific time is calculated and then assigned to the process as the when field of the message. Then find the appropriate location in the MessageQueue (arrange the when small to large arrangement) and insert the message into the MessageQueue. In this way, MessageQueue is a linked list structure arranged according to message time.

3. How to get messages from messagequeue?

The message is obtained internally through the next() loop, which is to ensure that a message must be returned. If no message is available, it is blocked here until a new message arrives.

The nativePollOnce method is the blocking method, and nextPollTimeoutMillis is the blocking time

When will it be blocked? Two cases:

1. There are messages, but the current time is less than the message execution time

if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
//At this time, the blocking time is the message time minus the current time, and then enter the next cycle to block.

2. When there is no news

 nextPollTimeoutMillis = -1;//Indicates that it has been blocked

4. What happens when there is no message in the messagequeue and how to wake up after blocking? And pipe/epoll mechanism?

When the message is unavailable or there is no message, it will be blocked in the next method, and the blocking method is through the pipe/epoll mechanism

It is an IO multiplexing mechanism. The specific logic is that a process can monitor multiple descriptors. When a descriptor is ready (generally read ready or write ready), it can notify the program to perform the corresponding read-write operation. This read-write operation is blocked. In Android, a Linux Pipe is created to handle blocking and wakeup.

  • When the message queue is empty and the reader of the pipeline waits for new content in the pipeline to be readable, it will enter the blocking state through the epoll mechanism.
  • When there is a message to be processed, it will write content through the write end of the pipeline to wake up the main thread.

5. How are synchronous barriers and asynchronous messages implemented and what are their application scenarios?

Detailed introduction to synchronization barrier blog:

There are three message types in the Handler:

  • Synchronization message: normal message
  • Asynchronous message: a message set through setAsynchronous(true)
  • Synchronization barrier message: a message added through the postSyncBarrier method. The characteristic is that the targe t is empty, that is, there is no corresponding handler.

What is the relationship between the three?

  • Under normal circumstances, both synchronous messages and asynchronous messages are processed normally, that is, messages are retrieved and processed according to the time when.
  • When the synchronization barrier message is encountered, it starts to look for the asynchronous message from the message queue, and then determines whether to block or return the message according to the time.

In other words, the synchronization barrier message will not be returned. It is just a flag and a tool. When it is encountered, it means to process the asynchronous message first. Therefore, the significance of the existence of synchronous barriers and asynchronous messages is that some messages need "urgent processing"

Application scenario:

In UI drawing: scheduleTraversals

 void scheduleTraversals() {
   if (!mTraversalScheduled) {
       mTraversalScheduled = true;
       // The synchronization barrier blocks all synchronization messages
       mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
       // Send drawing tasks through Choreographer
       mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
   }
}
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);

6. How to process and reuse message after it is distributed?

After executing the dispatchMessage, there will be: msg.recycleUnchecked(), empty all parameters of msg, release all resources, and insert the current empty message into the sPool header.

sPool is a message object pool. It is also a message with a linked list structure. The maximum length is 50.

**Message reuse process: * * it is directly obtained from the message pool. If it is not obtained, it will be re created.

Looper related:

1. The role of Looper? How do I get the Looper of the current thread? Why not use Map to store threads and objects directly?

Looper is a role that manages message queues. It will constantly find messages from the MessageQueue, that is, the loop method, and return the messages to the Handler for processing, and the loop is obtained from ThreadLocal.

2. How does ThreadLocal work? What are the benefits of this design?

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

public void set(T value) {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null)
      map.set(this, value);
   else
   createMap(t, value);
}

In each Thread, there is a threadLocals variable, which stores ThreadLocal and the corresponding objects to be saved. The advantage of this is that the same ThreadLocal object is accessed in different threads, but the values obtained are different. The internal obtained maps are different. Map and Thread are bound. Therefore, although the same ThreadLocal object is accessed, the accessed maps are not the same, so the obtained values are also different.

Why not use Map to store threads and objects directly?

For example:

  • ThreadLocal is the teacher.
  • Thread is the classmate.
  • Looper (required value) is a pencil.

Now the teacher bought a batch of pencils and wanted to send them to the students. How? Two approaches:

  • 1. The teacher wrote the name of each student on each pencil, put it in a big box (map), and let the students find it by themselves when they use it.

This method is to store the students and pencils in the Map, and then use the students to find pencils from the Map.

This approach is a bit like using a Map to store all threads and objects. The bad thing is that it will be very chaotic. There is a connection between each thread, which is also easy to cause memory leakage.

  • 2. The teacher sends each pencil directly to each student and puts it in the student's pocket. When using it, each student can take out the pencil from his pocket.

This method is to store the teacher and pencil in the Map, and then when you use it, the teacher says that the students just need to take it out of their pocket. Obviously, this method is more scientific, which is ThreadLocal. Because the pencil itself is used by the students themselves, it is best to give the pencil to the students for safekeeping at the beginning, and each student shall be isolated.

3. What other scenarios will use ThreadLocal?

Choreographer is mainly used by the main thread to cooperate with VSYNC interrupt signal. Therefore, using ThreadLocal here is more meaningful to complete the function of thread singleton.

4. The creation method of looper and the function of the quitAllow field?

Only one Looper can be created for the same thread. If it is created multiple times, an error will be reported.

quitAllow is whether exit is allowed.

When is the quit method usually used?

  • In the main thread, you can't exit under normal circumstances, because the main thread stops after exiting. Therefore, when the APP needs to exit, it will call the quit method, and the message involved is EXIT_APPLICATION, you can search.
  • In the child thread, if all messages are processed, you need to call the quit method to stop the message loop.

5.Looper is an internal loop. Why won't it get stuck?

  • 1. The main thread itself needs to run only because it has to deal with various views and interface changes. Therefore, this loop is needed to ensure that the main thread will continue to execute and will not be exited.
  • 2. The operation that will really get stuck is that the operation time is too long when a message is processed, resulting in frame loss and ANR, rather than the loop method itself.
  • 3. In addition to the main thread, there will be other threads to handle events that accept other processes, such as Binder thread (application thread), which will accept events sent by AMS
  • 4. After receiving the cross process Message, it will be handed over to the Hanlder of the main thread for Message distribution. Therefore, the life cycle of the activity depends on the loop.loop of the main thread. When different messages are received, corresponding measures are taken, such as msg=H.LAUNCH_ACTIVITY, then call the ActivityThread.handleLaunchActivity() method, and finally execute to the onCreate method.
  • 5. When there is no message, it will be blocked in the nativePollOnce() method in queue.next() of loop. At this time, the main thread will release CPU resources and enter the sleep state until the next message arrives or a transaction occurs. Therefore, the dead loop will not consume CPU resources in particular.

Handler related:

1. How does message distribute and bind to Handler?

Distribute messages through msg.target.dispatchMessage(msg).

When using Hanlder to send messages, msg.target = this will be set, so target is the Handler that added the message to the message queue.

2. What is the difference between post and sendMessage in handler?

The main sending messages in Hanlder can be divided into two types:

  • post(Runnable)
  • sendMessage
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
   Message m = Message.obtain();
   m.callback = r;
   return m;
}

The difference between post and sendMessage is that the post method sets a callback for the Message.

Information processing method dispatchMessage:

    public void dispatchMessage(@NonNull Message msg) {
        //1. If msg.callback is not null, when sending a message through the post method, the message will be sent to this msg.callback for processing, and then there will be no follow-up.
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //2. If msg.callback is empty, that is, when sending a message through sendMessage, it will judge whether the current mCallback of the Handler is empty. If not, it will be handed over to the Handler.Callback.handleMessage for processing.
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //3. If mCallback.handleMessage returns false, call the handleMessage method overridden by the handler class.
            handleMessage(msg);
        }
    }

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

Therefore, the difference between post(Runnable) and sendMessage lies in the processing method of subsequent messages, whether to give it to msg.callback or Handler.Callback or Handler.handleMessage.

3. What is the difference between handler.callback.handlemessage and Handler.handleMessage?

The difference is whether the Handler.Callback.handleMessage method returns true:

  • If true, Handler.handleMessage will no longer be executed
  • If false, both methods are executed.

expand:

1.IdleHandler function and usage scenario?

When there is no message in the MessageQueue, it will be blocked in the next method. In fact, before blocking, the MessageQueue will check whether there is an IdleHandler. If so, it will execute its queueIdle method.

 private IdleHandler[] mPendingIdleHandlers;

    Message next() {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                //1. When the message is executed, that is, the current thread is idle, set pendingIdleHandlerCount
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                //2. Initialize mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //Convert mIdleHandlers to array
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Traverse the array and process each IdleHandler
            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 the queueIdle method returns false, the IdleHandler will be deleted after processing
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

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

When there is no message processing, it will process each IdleHandler object in the millehandlers collection and call its queueIdle method. Finally, judge whether to delete the current IdleHandler according to the return value of queueIdle.

IdleHandler can handle some idle tasks before blocking when there are no messages to be processed in the message queue.

Common application scenarios:

Start optimization.

  • We usually put some events (such as drawing and assignment of interface view) into onCreate method or onResume method. But these two methods are all invoked before the interface is drawn, that is to say, the time consuming of these two methods will affect the start-up time to a certain extent. Therefore, we can put some operations into IdleHandler, that is, call them after the interface drawing is completed, so as to reduce the startup time

2.HandlerThread principle and usage scenario?

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }
    
}    

HandlerThread is a Thread class that encapsulates Looper.

This is to make it easier for us to use Handler in sub threads.

The purpose of locking here is to ensure thread safety, obtain the Looper object of the current thread, and wake up other threads through notifyAll method after successful acquisition,

3.IntentService principle and usage scenario?

  • First, this is a Service
  • A HandlerThread is maintained internally, that is, a complete Looper is running.
  • The ServiceHandler of a child thread is also maintained.
  • After starting the Service, the onHandleIntent method will be executed through the Handler.
  • After completing the task, stopSelf will be automatically executed to stop the current Service.

Therefore, this is a Service that can perform time-consuming tasks in the sub thread and automatically stop after the task is executed

4. How does blockcanary work?

BlockCanary is a time-consuming third-party library used to detect application jams.

public static void loop() {
    for (;;) {
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
    }
}

There is a Printer class in the loop method, which prints the log twice before and after the dispatchMessage processes the message. Replace this log class Printer with our own Printer,

5. The memory leak of the handler?

6. How to use Handler to design an App that does not crash?

The main thread crash actually occurs in the processing of messages, including life cycle and interface drawing. So if we can control this process and restart the message loop after a crash, the main thread can continue to run

Handler(Looper.getMainLooper()).post {
        while (true) {
            //Main thread exception interception
            try {
                Looper.loop()
            } catch (e: Throwable) {
            }
        }
}

3. Source code analysis

1.Looper

To use the message mechanism, first create a Looper. When the initialization Looper has no parameters, prepare(true) is called by default; It means that the Looper can exit, and false means that the current Looper cannot exit.

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

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

It can be seen here that loopers cannot be created repeatedly, only one can be created. Create Looper and save it in ThreadLocal. ThreadLocal is Thread Local Storage (TLS). Each thread has its own private local storage area. Different threads cannot access each other's TLS area.

Open Looper

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

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) { //Enter the main loop method of loop
        Message msg = queue.next(); //It may block because the next() method may loop indefinitely
        if (msg == null) { //If the message is empty, exit the loop
            return;
        }
		//`BlockCanary ` is a time-consuming third-party library used to detect application jams. The custom Printer is set
        Printer logging = me.mLogging;  //The default value is null. The output can be specified through the setMessageLogging() method for the debug function
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //Get the target Handler of msg and use it to distribute messages 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
         
        }
        msg.recycleUnchecked(); 
    }
}

loop() enters the loop mode and repeats the following operations until the Message is empty: read the next Message of MessageQueue (about next(), which will be described in detail later); Distribute the Message to the corresponding target.

When next() takes out the next message and there is no message in the queue, next() will loop indefinitely and cause blocking. Wait for a message to be added to the MessageQueue, and then wake up again.

The main thread does not need to create a Looper by itself, because the system has automatically called the Looper.prepare() method when the program starts. View the main() method in ActivityThread. The code is as follows:

  public static void main(String[] args) {
		..........................
        Looper.prepareMainLooper();
  		..........................
        Looper.loop();
 		 ..........................
    }

The prepareMainLooper() method calls the prepare(false) method.

2.Handler

Create Handler

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
   .................................
    //Loop. Prepare() must be executed before obtaining the loop object, otherwise it is null
    mLooper = Looper.myLooper();  //Gets the Looper object from the TLS of the current thread
    if (mLooper == null) {
        throw new RuntimeException("");
    }
    mQueue = mLooper.mQueue; //Message queue from Looper object
    mCallback = callback;  //Callback method
    mAsynchronous = async; //Set whether the message is processed asynchronously
}

For the parameterless construction method of Handler, the Looper object in the TLS of the current thread is adopted by default, the callback callback method is null, and the message is processed synchronously. As long as the Looper.prepare() method is executed, a valid Looper object can be obtained.

3. Send message

There are several ways to send messages, but in the final analysis, the sendMessageAtTime() method is called. When sending a message through the Handler's post() method or send() method in the child thread, the sendMessageAtTime() method is finally called.

post method

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
 public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
 public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

send method

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
 public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    } 
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
 public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Even updating the UI in the runOnUiThread() called Activity in the child thread is actually sending the message to notify the main thread to update UI, and eventually calls the sendMessageAtTime() method.

 public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

If the current thread is not equal to the UI thread (main thread), call the post() method of the Handler, and eventually call the sendMessageAtTime() method. Otherwise, the run() method of the Runnable object is called directly. Let's find out what the sendMessageAtTime() method does?

sendMessageAtTime()

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
       //Where mQueue is the message queue obtained from Looper
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //Call enqueueMessage method
        return enqueueMessage(queue, msg, uptimeMillis);
    }
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //Call enqueueMessage method of MessageQueue
        return queue.enqueueMessage(msg, uptimeMillis);
    }

You can see that the sendMessageAtTime() method is very simple. It is to call the enqueueMessage() method of MessageQueue to add a message to the message queue.
Let's look at the specific execution logic of the enqueueMessage() method.
enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    // Each Message must have a target
    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.");
    }
    synchronized (this) {
        if (mQuitting) {  //When exiting, recycle msg and join the message pool
            msg.recycle();
            return false;
        }
        msg.markInUse();//Mark as used
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //If p is null (indicating that there is no message in the MessageQueue) or the trigger time of msg is the earliest in the queue, enter the branch
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; 
        } else {
            //Insert messages into the MessageQueue in chronological order. Generally, you do not need to wake up the event queue unless
            //There is a barrier in the Message queue header, and 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;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue is arranged according to the sequence of Message trigger time. The Message at the head of the queue is the Message to be triggered the earliest. When a Message needs to be added to the Message queue, it will traverse from the head of the queue until it finds the appropriate location where the Message should be inserted, so as to ensure the time sequence of all messages.

4. Get message

After sending a message, the message queue is maintained in the MessageQueue, and then the loop() method is used in the loop to continuously obtain messages. The loop() method is introduced above, and the most important thing is to call the queue.next() method to extract the next message. Let's take a look at the specific process of the next() method.
next()

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //When the message loop has exited, it returns directly
        return null;
    }
    int pendingIdleHandlerCount = -1; // The initial value is - 1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //Blocking operation will be returned when waiting for nextPollTimeoutMillis for a long time or when the message queue is awakened
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //When the message Handler is empty, query the next asynchronous message msg in the MessageQueue. If it is empty, exit the loop. It is used together with the synchronization barrier.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //When the asynchronous message triggering time is greater than the current time, set the timeout length of the next polling
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Get a message and return
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //Set the usage status of the message, that is, flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //Successfully obtained the next message to be executed in MessageQueue
                }
            } else {
                //No news
                nextPollTimeoutMillis = -1;
            }
         //Message exiting, null returned
            if (mQuitting) {
                dispose();
                return null;
            }
            ...............................
    }
}

nativePollOnce is a blocking operation, where nextPollTimeoutMillis represents the length of time to wait before the next message arrives; when nextPollTimeoutMillis = -1, it indicates that there is no message in the message queue and will wait all the time. You can see next() Method obtains the next message to be executed according to the trigger time of the message. When the message in the queue is empty, blocking operation will be performed.

5. Distribute messages

In the loop() method, after getting the next message, execute msg.target.dispatchMessage(msg) to distribute the message to the target Handler object.
Let's take a look at the execution process of the dispatchMessage(msg) method.

dispatchMessage()

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //When the Message has a callback method, call back the msg.callback.run() method;
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //When the Callback member variable exists in the Handler, the Callback method handleMessage();
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //Handler's own callback method handleMessage()
        handleMessage(msg);
    }
}
private static void handleCallback(Message message) {
        message.callback.run();
    }

Distribution message process:

When msg.callback of Message is not empty, the callback method msg.callback.run();
When the Handler's mCallback is not empty, the callback method mCallback.handleMessage(msg);
Finally, calling the callback method handleMessage() of Handler itself, the method is empty by default, and the Handler subclass completes the specific logic by overwriting this method.

Priority of message distribution:

Callback method of Message: message.callback.run(), with the highest priority;
Callback method of callback in Handler: Handler.mCallback.handleMessage(msg), with priority next to 1;
The default method of Handler: Handler.handleMessage(msg), with the lowest priority.

In many cases, the processing method after message distribution is the third case, that is, Handler.handleMessage(). Generally, this method is overridden to realize its own business logic

Tags: Java Android Interview

Posted on Mon, 11 Oct 2021 17:51:24 -0400 by php_coder_dvo