Handler, Looper and MessageQueue source code analysis

Handler, Looper and MessageQueue source code analysis

In Android, Handler can be used to update the changes of UI in the main thread. The UI can only be updated in the main thread. In order to allow other threads to control UI changes, Android provides a mechanism for Handler, Looper and MessageQueue to work together to achieve the purpose of updating UI in other threads.

Generally, we define a Handler in the main thread by the following method

  1. private Handler mHandler = new Handler() {
  2. @Override
  3. public void handleMessage(Message msg) {
  4. tv.setText( "mHandler change UI" );
  5. super.handleMessage(msg);
  6. }
  7. };

Looper and MessageQueue are not usually seen, so where are they called and how do they collaborate? Looper is not called explicitly in the main thread but is called by default in the ActivityThread.main method.

  1. public   static void main(String[] args) {
  2. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain" );
  3. SamplingProfilerIntegration.start();
  4.   
  5. // CloseGuard defaults to   true   and can be quite spammy. We
  6. // disable it here, but selectively enable it later (via
  7. // StrictMode) on debug builds, but using DropBox, not logs.
  8. CloseGuard.setEnabled( false );
  9.   
  10. Environment.initForCurrentUser();
  11.   
  12. // Set the reporter for event logging in libcore
  13. EventLogger.setReporter(new EventLoggingReporter());
  14.   
  15. // Make sure TrustedCertificateStore looks in the right place for CA certificates
  16. final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
  17. TrustedCertificateStore.setDefaultUserDirectory(configDir);
  18.   
  19. Process.setArgV0( "<pre-initialized>" );
  20.   
  21. Looper.prepareMainLooper();//Create Looper
  22.   
  23. ActivityThread thread = new ActivityThread();
  24. thread.attach( false );
  25.   
  26. if (sMainThreadHandler == null ) {
  27. sMainThreadHandler = thread.getHandler();
  28. }
  29.   
  30. if ( false ) {
  31. Looper.myLooper().setMessageLogging(new
  32. LogPrinter(Log.DEBUG, "ActivityThread" ));
  33. }
  34.   
  35. // End   of event ActivityThreadMain.
  36. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  37. Looper.loop(); //Open Looper loop
  38.   
  39. throw new RuntimeException( "Main thread loop unexpectedly exited" );
  40. }

As shown in the above code, the Looper.prepareMainLooper() method is called to create a Looper in the main thread. If you don’t believe it, let’s check what this method does.

Looper

prepare

  1. public   static void prepare () {
  2. prepare ( true );
  3. }
  4.   
  5. private static void prepare (boolean quitAllowed) {
  6. if (sThreadLocal.get() != null ) {
  7. throw new RuntimeException( "Only one Looper may be created per thread" );
  8. }
  9. sThreadLocal.set (new Looper(quitAllowed)); //Create Looper and assign it to sThreadLocal
  10. }
  11.   
  12. /**
  13. * Initialize the current thread as a looper, marking it as an
  14. * application's main looper. The main looper for your application
  15. * is created by the Android environment, so you should never need
  16. * to call this function yourself. See also: {@link # prepare ()}
  17. */
  18. public   static void prepareMainLooper() {
  19. prepare ( false );
  20. synchronized (Looper.class) {
  21. if (sMainLooper != null ) {
  22. throw new IllegalStateException( "The main Looper has already been prepared." );
  23. }
  24. sMainLooper = myLooper();
  25. }
  26. }
  27.       
  28. public   static @Nullable Looper myLooper() {
  29. return sThreadLocal.get();
  30. }

In the prepareMainLooper method, prepare is called. Through prepare, you will find that it actually creates a Looper and assigns it to sThreadLocal. At the same time, you can get the Looper in the current thread through the myLooper method. Let's take a look at what new Looper(quitAllowed) initializes

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

Here we finally see MessageQueue, which creates a MessageQueue. This message queue is used to save subsequent Messages. Going back to the ActivityThread.main method, we find that it calls Looper.loop() to start the Looper loop and listen to the messages in the message queue MessageQueue.

loop

Let's look at the source code of Looper.loop():

  1. public   static void loop() {
  2. final Looper me = myLooper(); //Get Looper
  3. if (me == null ) {
  4. throw new RuntimeException( "No Looper; Looper.prepare() wasn't called on this thread." );
  5. }
  6. final MessageQueue queue = me.mQueue; //Get the message queue
  7.   
  8. // Make sure the identity of this thread is that of the local process,
  9. // and keep track of what that identity token actually is .
  10. Binder.clearCallingIdentity();
  11. final long ident = Binder.clearCallingIdentity();
  12.   
  13. for (;;) {
  14. Message msg = queue. next (); // might block
  15. if (msg == null ) {
  16. // No message indicates that the message queue is quitting.
  17. return ;
  18. }
  19.   
  20. // This must be in a local variable, in   case a UI event sets the logger
  21. final Printer logging = me.mLogging;
  22. if (logging != null ) {
  23. logging.println( ">>>>> Dispatching to " + msg.target + " " +
  24. msg.callback + ": " + msg.what);
  25. }
  26.   
  27. final long traceTag = me.mTraceTag;
  28. if (traceTag != 0) {
  29. Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
  30. }
  31. try {
  32. msg.target.dispatchMessage(msg); //Distribute messages through Handler
  33. finally
  34. if (traceTag != 0) {
  35. Trace.traceEnd(traceTag);
  36. }
  37. }
  38.   
  39. if (logging != null ) {
  40. logging.println( "<<<<< Finished to " + msg.target + " " + msg.callback);
  41. }
  42.   
  43. // Make sure that during the course of dispatching the
  44. // identity of the thread wasn't corrupted.
  45. final long newIdent = Binder.clearCallingIdentity();
  46. if (ident != newIdent) {
  47. Log.wtf(TAG, "Thread identity changed from 0x"  
  48. + Long.toHexString(ident) + " to 0x"  
  49. + Long.toHexString(newIdent) + " while dispatching to "  
  50. + msg.target.getClass().getName() + " "  
  51. + msg.callback + " what=" + msg.what);
  52. }
  53.   
  54. msg.recycleUnchecked();
  55. }
  56. }

In the loop, we first get the Looper of the current thread, and also get the MessageQueue in the Looper, which means that the Looper has been bound to the current thread. Then we start a for infinite loop, and find that it is constantly taking messages from the message queue, and finally passing them to msg.target to call its dispatchMessage method. So what is target? Let's go into Message

Message

  1. /*package*/ int flags;
  2.  
  3. /*package*/ long when ;
  4.      
  5. /*package*/ Bundle data;
  6.      
  7. /*package*/ Handler target;
  8.      
  9. /*package*/ Runnable callback;
  10.      
  11. // sometimes we store linked lists of these things
  12. /*package*/ Message next ;

We find that it is the Handler we are familiar with, which means that the dispatchMessage method in the Handler is called to distribute the message. In this way, the Handler contacts the thread bound to the Looper through the Looper, which is the main thread.

Handler

  1. public Handler(Callback callback, boolean async) {
  2. if (FIND_POTENTIAL_LEAKS) {
  3. final Class<? extends Handler> klass = getClass();
  4. if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  5. (klass.getModifiers() & Modifier. STATIC ) == 0) {
  6. Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  7. klass.getCanonicalName());
  8. }
  9. }
  10.   
  11. mLooper = Looper.myLooper();
  12. if (mLooper == null ) {
  13. throw new RuntimeException(
  14. "Can't create handler inside thread that has not called Looper.prepare()" );
  15. }
  16. mQueue = mLooper.mQueue;
  17. mCallback = callback;
  18. mAsynchronous = async;
  19. }

Through the initialization of Handler, it obtains the Looper of the thread it is in, and also obtains the message queue in the Looper. Of course, if the Looper of the thread is empty, an exception will be thrown, which explains why when creating a Handler in a non-main thread, you need to call Looper.prepare and Looper.loop respectively, but the main thread does not need to, because it has been called by default.

dispatchMessage

  1. public void dispatchMessage(Message msg) {
  2. if (msg.callback != null ) {
  3. handleCallback(msg);
  4. } else {
  5. if (mCallback != null ) {
  6. if (mCallback.handleMessage(msg)) {
  7. return ;
  8. }
  9. }
  10. handleMessage(msg);
  11. }
  12. }
  13. private static void handleCallback(Message message) {
  14. message.callback.run();
  15. }

Back to the front, for the processing of dispatchMessage, first determine whether msg.callback is empty. Here, callback should be known as a Runnable through the above Message. If it is not empty, directly call the run method of Runnable. Otherwise, call the handleMessage method of Handler. I believe everyone is already familiar with this method. The processing of events is performed in this method. Because we already know that the Handler has contacted the main thread, the processing in handleMessage is naturally relative to the main thread, and the UI can be updated naturally. Through this, we can compare Looper to a bridge to connect the communication between the thread where the Looper is located and the Handler, and manage the messages in the message queue MessageQueue. So how is the previous Runnable not empty? There are two ways to use Handler. One is to directly create a Handler and rewrite its handleMessage method, and the other can be used through Handler.post(Runnable), so that the processing of events is naturally implemented in the run method.

The above describes how the Handler contacts the thread that needs to be operated and how the message is retrieved and processed. Next, let's talk about how the message is put into the MessageQueue in the Looper.

sendMessageAtTime

There are many ways to send messages through Handler, such as sendMessage, sendEmptyMessage and sendMessageDelayed, but in fact, they all call the sendMessageAtTime method. So let's take a look at the implementation of the sendMessageAtTime method.

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2. MessageQueue queue = mQueue;
  3. if (queue == null ) {
  4. RuntimeException e = new RuntimeException(
  5. this + " sendMessageAtTime() called with no mQueue" );
  6. Log.w( "Looper" , e.getMessage(), e);
  7. return   false ;
  8. }
  9. return enqueueMessage(queue, msg, uptimeMillis);
  10. }

And sendMessageAtTime calls the enqueueMessage operation. From the name of this method, you can tell that it is an enqueue operation.

enqueueMessage

  1. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  2. msg.target = this;
  3. if (mAsynchronous) {
  4. msg.setAsynchronous( true );
  5. }
  6. return queue.enqueueMessage(msg, uptimeMillis);
  7. }

As expected, queue.enqueueMessage(msg, uptimeMillis) in MessageQueue is directly called to add the message to the message queue. At the same time, this code msg.target = this assigns the current Handler to msg.target, which is the Handler called in the Looper.loop method mentioned earlier. In this way, the message is placed in the MessageQueue, and then the message is taken out through the loop mentioned earlier for corresponding processing, thus forming the entire system for processing messages. This is also the principle of using Handler inside. Well, these are basically the connections between Handler, Looper and MessageQueue. I also drew a simple picture and hope it will be helpful.

Summarize

Let's summarize the process between them. First, create a Handler. There must be a Looper in the thread where the Handler is located. If it is implemented by default in the main thread, other threads must call Looper.prepare to create a Looper and call Looper.loop to start processing messages. Each Looper has a MessageQueue, which is used to store Messages. The Handler puts the message into the message queue through a series of operations such as post or send.., and the Looper keeps listening to the message processing by starting a continuous loop, constantly taking messages from the MessageQueue, and handing them over to the dispatchMessage of the handler bound to the current Looper for distribution. ***According to the situation, the run method of Runnable or the HandlerMessage method of Handler is called to ***process the message.

Other sharing: https://idisfkj.github.io/arc...

<<:  A Preliminary Study on WeChat Mini Programs

>>:  Big data prescription for startups | WOT Technology Clinic Second Phase Diagnosis

Recommend

How to achieve user experience beyond expectations?

Each product iteration requires that the new user...

3 basic elements of community operation and 11 cases

Community is an area that almost all Internet pro...

Mafengwo Product Analysis

Many people yearn for poetry and distant places, ...

Should I open the windows? — A bizarre COVID-19 outbreak

After three years of fighting the epidemic, weari...

Volvo seeks independent listing after seven years of itch

For Li Shufu, who has just become the richest man...