This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

Handler is an essential part of Android development projects, and is also an important part of the Android system internal thread communication, but how many people fully understand it, the following is for it, put forward some common questions, and answer, just for your reference ~

  1. Describe the Handler mechanism in general.
  2. Where does Looper exist? How can thread uniqueness be guaranteed?
  3. How do you understand the role of ThreadLocal?
  4. What are the similarities and differences between Main Looper and regular Looper?
  5. How does Handler or Looper switch threads?
  6. Why doesn’t Looper’s loop() get stuck?
  7. How is Looper’s wait accurately awakened?
  8. How to get Message? Why?
  9. How does MessageQueue manage messages?
  10. Understand the similarities and differences between Message and MessageQueue?
  11. How are execution moments of Message managed?
  12. What is the relationship between Handler, Mesage, and Runnable?
  13. IdleHandler idle Message What’s the use?
  14. Asynchronous messages or synchronous barriers? How does it work? How does that work?
  15. Looper and MessageQueue, Message and Handler?
  16. What are the roles of Native side NativeMessageQueue and Looper?
  17. How does the Native side use Looper?
  18. Why does Handler cause memory leaks? How to avoid it?
  19. Handler is used in the system
  20. Why doesn’t Android allow concurrent access to the UI?

1. Describe the general mechanism of the Handler mechanism.

  1. Looper prepares and starts round robin:

    • If there is no Message, call NativepollOnce()Enter theInfinite wait
    • Message exists, but execution timewhenIf not, call pollOnce() and pass in the remaining time parameterLimited waiting
    • Looper#prepare()Unique to the initialization threadLooperAs well asMessageQueue
    • Looper#loop()openInfinite loopRead the next Message in a MessageQueue that meets the execution time
  2. Message sending, joining and leaving:

    • Native side if in infinite wait: any thread directionHandlersendMessage 或 RunnableAfter that, the Message is inserted into the MessageQueue corresponding to the Looper instance held by the Handler in the order of the WHEN conditionProper position. MessageQueue will call the Native side when it finds an appropriate Message to insertwake()Wake up an infinite waiting thread. This will cause MessageQueue reading to continueEnter the next cycleAll messages in the Queue that meet the criteria are returned to Looper
    • If the Native side is in finite wait: epoll_wait returns after the specified wait time. The thread continues to read the MessageQueue, which is now dequeued because the duration condition is met
  3. Looper handles Message implementation:

    Looper gets the Message and calls the Message callback property (Runnable) or the target property (Handler) to execute the Handler callback.

    • Handler$Callback if the mCallback attribute exists

    • Instead, call handleMessage()

2. Where does Looper live? How can thread uniqueness be guaranteed?

  • Looper instances are managed in static propertiessThreadLocal 中
  • ThreadLocalThrough internalThreadLocalMapHold the stars,keyIs the ThreadLocal instance itself,valueIs the Looper instance
  • Each Thread has its own ThreadLocalMap, which ensures that each Thread corresponds to a separate Looper instancemyLooper()You can get thread-specific Looper

Easter eggs: How many Looper instances does an App have? How many ThreadLocal instances? How many instances of MessageQueue? How many Message instances? Several Handler instances

  • There is only one Looper instance per thread
  • A Looper instance corresponds to only one MessageQueue instance
  • A MessageQueue instance can correspond to multiple Message instances, which are retrieved from the Message static pool with an upper limit of 50
  • A thread can have multiple instances of handlers, whose handlers are simply entry and exit points for sending and executing task logic
  • ThreadLocal instances are static and are shared by the entire process. Each ThreadLocalMap stored by Looper weakly references it as a key

3. How do you understand the role of ThreadLocal?

  • The first thing to make clear is that it’s not used to switch threads,Just so that each thread convenience gets its own Looper instance, see Looper#myLooper()
    • The Handler can then specify which Looper thread it belongs to during initialization
    • It can also be used by a thread to determine if it is the main thread

4. What are the similarities and differences between Main Looper and general Looper?

  • The difference between:
    1. Main Looper Cannot quit

      The main thread, which requires constant reading of system messages and book input, is an entry point to the process and can only be terminated directly by the system. In turn, its Looper is created with a no-quit flag set, while loopers of other threads can and must quit manually

    2. The Main Looper instance is also statically cached

      To make it easier for each thread to get the MainLooper instance, see Looper#getMainLooper(). The MainLooper instance is also cached in the Looper class as the sMainLooper property.

  • Similarities:
    1. Both instances are created by indirectly calling the Looper constructor with Looper#prepare()
    2. Are managed by static ThreadLocal, making it easier for each thread to get its own Looper instance

Easter egg: Why doesn’t the main thread initialize Looper?

The entry point to the App is not MainActivity or Application, but ActivityThread.

In order for the Application, ContentProvider, Activity and other components to run, it must start the Looper mechanism that continuously accepts input. So prepareMainLooper() is called at the end of the main() execution to create the Looper and loop() is called.

We don’t need to call it, we can’t call it.

We can say that our component would not have run if the main thread had not created a Looper!

5. How does Handler (or Looper) switch threads?

  1. The Handler is created to specify the Looper of the thread it belongs to, which in turn holds the MessageQueue unique to the Looper
  2. Looper#loop() keeps reading the appropriate Message in MessageQueue and enters the wait if there are no messages
  3. When a Message or Runnable is sent to the Handler, the Message is inserted into the holding MessageQueue
  4. When the Message arrives and meets the criteria, the thread to which the MessageQueue belongs wakes up and the Message is returned to Looper
  5. Looper then calls back to the Handler Callback or Runnable that Message points to to switch the thread

In short, sending a Message to Handler is actually inserting a Message into the MessageQueue unique to the thread that the Handler belongs to. The thread-specific Looper keeps reading the MessageQueue. So after sending a Message to another thread’s Handler, that thread’s Looper will automatically respond.

Why doesn’t the Looper loop get stuck?

In order for the main thread to continue processing user input, loop() is an infinite loop that keeps calling MessageQueue#next() to read the appropriate Message.

When there is no Message, pollOnce() is called and Linux’s epoll mechanism waits and frees the resource. At the same time, eventFd listens for write events when Message arrives and wakes up.

This frees resources when idle, does not block threads, and continues to receive input.

Egg 1: Why can’t the processing after loop() be executed

Because loop() is an infinite loop until the previous processing before and after quit cannot be executed, avoid placing processing after loop().

Easter Egg 2: What is the state of the thread while Looper is waiting?

Call Linux’s epoll mechanism to wait, and in fact the Java side prints the thread’s status. You’ll find that the thread is in a Runnable state, but CPU resources are temporarily freed.

7. How can Looper’s wait be awakened accurately?

MessageQueue#next(), which reads the appropriate Message, waits either because there is no Message or because the execution condition has not been met:

  • Infinite wait

    PollOnce () on the Natvie side passes -1 if there is no Message (no Message in the queue or a synchronization barrier is established but no asynchronous Message).

    Linux executes epoll_wait() into an infinite wait, which waits for the appropriate Message to be inserted and then calls wake() on the Native side to write to the wake FD to trigger the next loop to wake up the MessageQueue read

  • Limited waiting

    In the case of finite wait, the next Message remaining time is given as an argument to epoll_wait(), and epoll will wait for some time and then automatically return to the next cycle read by MessageQueue

8. How to get Message? Why?

  • Share design pattern: Static method through Messageobatin()Get, because the method is not mindless new, butGet an instance from a single list poolAnd, inrecycle()And then I put it back in the pool
  • The benefit is that Message instances can be reused more efficiently for frequent Message usage scenarios
  • Of course, there is an upper limit of 50 for the cache pool, because there is no need to cache indefinitely, which is itself wasteful
  • Note that the cache pool is static, that is, the entire process shares a cache pool

9. How does MessageQueue manage messages?

  • MessageQueue manages messages through a single linked list, which is thread-specific rather than a Message Pool shared by processes
  • Messages are queued and dequeued by their execution time when
  • MessageQueue manages not only messages but also idle handlers and synchronization barriers

10. Understand the similarities and differences between Message and MessageQueue?

  • Similarities: Message instances are managed through singly linked lists;
    • Message obtains insert nodes into a singly linked list using obtain() and recycle()
    • MessageQueue fetches and inserts nodes into single-linked lists via enqueueMessage() and next()
  • The difference between:
    • Message singly linked lists are static, cache pools used by processes
    • MessageQueue single-linked lists are non-static and are used only by Looper threads

11. How to manage the execution time of Message?

  • Messages are sent by execution timewhenAttribute sequencing is managed in MessageQueue
    • When of the delayed Message is equal toThe current time of the invocationanddelayThe sum of the
    • When for a non-delayed Message equalsThe current moment(delay for0)
    • Queue-jumping Message when is fixed to0To facilitate insertion into the queuehead
  • MessageQueue then compares the read time and when
    • When has arrived out of the queue,
    • The interpolation between the current time and the target when is calculated and sent to Native to wait for the corresponding time. When the time is up, it will automatically wake up and continue to read Message

In fact, neither Message is guaranteed to execute at its corresponding WHEN, and tends to be delayed! This is because the next Message in the queue must wait for the current Message to finish processing.

For example, non-delayed messages are sent, where when is the moment they are sent, but they do not execute immediately. Each queue has to wait for the main thread’s existing Message to run out, and when these tasks have finished, the “WHEN” moment has passed. The delay is even more pronounced if there are other messages at the front of the queue!

Egg: will sending a large number of messages to Handler in.oncreate () cause the main thread to stall?

No, the large number of messages sent are not executed immediately, they are just queued first.

OnCreate (), and the onStart() and onResume() processes that are then called synchronously, are also messages in nature. After the Message completes, the next Message reading loop will be used to call back the Message sent in onCreate.

It is important to note that if a FrontOfQueue is sent and a Message is inserted at the head of the queue, it will not be executed immediately because onStart and onResume are called synchronously after onCreate, essentially the same Message cycle

12. What is the relation between Handler, Mesage and Runnable?

  • As an entry point to use the Handler mechanism, the Handler is the starting point for sending a Message or Runnable
  • Send theA Runnable is also a Message in natureAs a matter of factcallbackProperty held
  • Handler is held in Mesage as a target property for Looper to call back when the Message execution condition is met

In fact, Handler is just an API for apps to use the Handler mechanism. In essence, Message is the more important carrier.

IdleHandler is idle Message. What’s the use?

  • This is suitable for tasks that are expected to be executed when idle, but do not affect main thread operations
  • System application:
    1. Activity destroyThe callback is inIdleHandler 中
    2. ActivityThread 中 GCHandlerIdleHandler is used and executed at idle timeGCoperation
  • An App:
    1. Send an IdleHandler that returns true and keeps a View flashing so that the user can be induced to click on the View when they are in a daze
    2. Putting part initialization in IdleHandler does not affect the start of the Activity

14. Asynchronous messages or synchronization barriers? How does it work? How does that work?

  • Asynchronous Message: SetisAsyncProperty
    • It can be sent using an asynchronous Handler
    • You can also call Message#setAsynchronous()Set it directly to asynchronous Message
  • Synchronous barrier: A Message with a null target is placed somewhere in the MessageQueue to ensure that subsequent non-asynchronous messages cannot be executed, only asynchronous messages can be executed
  • Principle: When a MessageQueue round Message finds that a synchronization barrier has been created, it will skip other messages, read the next async Message and execute, and the synchronization Message will be blocked until the barrier is removed
  • Application: For example, screen refresh Choreographer uses synchronization barriers to ensure that screen refresh events do not affect screen refresh due to queue load.
  • Note: The API for adding or removing synchronization barriers is not publicly available, and the App relies on reflection if needed

Looper and MessageQueue; Message and Handler.

  • Message is the carrier for the task and is used throughout the Handler mechanism
  • Handler is a public API responsible for sending messages and processing callback tasks. It is the producer of messages
  • MessagQueue manages incoming and outgoing messages. It is a container for messages
  • Looper is responsible for round-robin MessageQueue, keeping the thread running tasks continuously, and is the Message consumer

Easter egg: How to secure MessageQueue concurrent access?

Any thread can use Handler to produce a Message and put it into a MessageQueue, and the Looper to which Queue belongs keeps reading and trying to consume the Message. How to ensure that they do not generate deadlocks?

Looper will acquire the lock of MessageQueue before consuming the Message, but will release the lock before waiting for no Message or Message that has not met the condition. Specifically, nativePollOnce() is called outside the synchronized method block.

MessageQueue also needs to acquire the lock of MessageQueue before joining the queue. At this time, the Looper thread is waiting and does not hold the lock, which can ensure the successful joining of MessageQueue. After joining the queue, wake up and release the lock. After receiving the event writing, Native resumes reading MessagQueue and can get the lock, and successfully exits the queue.

This mechanism of waiting without holding a lock when there are no messages to consume avoids production and consumption deadlocks.

16. What are the functions of Native side NativeMessageQueue and Looper?

  • NativeMessageQueue is responsible for connecting to MessageQueue on the Java side for subsequent operationswait 和 wake, the FDS of Wake are then created and wait or wake up via the epoll mechanism.It does not manage Java messages
  • Native side also needs Looper mechanism, waiting and wake up needs the same, so this part of the implementation is encapsulated in JNI NativeMessageQueue and Native Looper, for Java and Native to use together

17. How to use Looper on Native side?

  • The Looper Native part is responsible for waiting and waking up Looper on the Java side. In addition, it provides Message,MessageHandler 或 WeakMessageHandler,LooperCallback 或 SimpleLooperCallbackSuch as API
  • These parts are available for Looper to be called directly by the Native side, for exampleInputFlingerLooper is widely used
  • The main method is to create Looper by calling the Looper constructor or prepare, then polling via poll, and then pollingsendMessage 或 addEventFd, waiting for Looper to wake up.The process is similar to the Java invocation

18. Why does Handler cause memory leaks? How to avoid it?

  • The inner class or inner class that holds the inner name of the Activity instance should have the same life cycle as the Activity or risk memory leaks.
  • If Handler is used incorrectly, this can lead to inconsistencies in the form of anonymous inner classes or handlers written to inner classes, Handler$Callback, Runnable, or activities that end with active Thread or Looper child threads
  • In this case, asynchronous tasks that are still active or messages that have been sent have not yet been processed, causing the lifetime of the inner class instance to be erroneously prolonged. An Activity instance that should be recycled is occupied by another Thread or MainLooper. (Active Thread or static sMainLooper is the GC Root object.)
  • Recommended practice:
    • Whether it’s Handler, Handler$Callback, or Runnable, use static inner class + weak reference notation to ensure that weak references are clear even when improper references occur
    • In addition, terminate the Thread, stop the child Thread’s Looper, or empty the Message in time when the Activity is destroyed. Make sure to completely disconnect activities from their GC Root reference source via Message (Message empties its reference to Handler, Thread terminates its GC Root reference).

Note that a static sThreadLocal instance does not hold the ThreadLocalMap that holds the Looper instance. Instead, it is held by Thread. In this sense, Looper can be held by active GC Root threads, which in turn can lead to memory leaks.

Easter Egg: Can netpassable Handler$Callback solve memory leaks?

Can’t.

A Callback written as an inner class or an anonymous inner class holds a reference to the Activity by default, and the Callback is held by the Handler. This will eventually result in the Message -> Handler -> Callback -> Activity chain remaining.

19. Application of Handler in the system

Particularly extensive, such as:

  • Manage the Activity lifecycle
  • Screen refresh
  • HandlerThread, IntentService
  • AsyncTask, etc.

The main use of the Handler switch thread, the main thread asynchronous Message important features. Note: Binder threads are not the main thread, but many operations such as life cycle management go back to the main thread, so many Binder calls go back to the main thread to perform subsequent tasks via handlers, such as ActviityThread$H, which is the extends Handler.

20. Why doesn’t Android allow concurrent access to the UI?

Android’s UI is not thread-safe and can cause data and display clutter if accessed concurrently.

But checks for this limitation start with ViewRootImpl#checkThread(), which is called on multiple UI accesses, such as a refresh, to check the current thread and throw an exception if it is not the main thread.

Note that ViewRootImpl is created after onResume(), so there is no error if the thread is started before onResume() is executed.

Egg: Is there a problem with the child thread updating the UI in onCreate()? Why is that?

Don’t.

Because exception detection is handled in ViewRootImpl, the instance is created and detected after onResume().

conclusion

Ability and energy is limited, if there are omissions, mistakes or unclear details, welcome to comment.

Let’s maintain these problems together and thoroughly understand the Handler mechanism!