Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article will also participate in the “Digitalstar Project” to win a creative gift package and challenge the creative incentive money!
We often use and refer to the Handler mechanism, which is unique to Android for communicating between threads, because it is so useful and important!
When you first started experimenting with the Handler mechanism, you thought the Handler class would play a big role. When you get into how it works, the Handler is just a call entry and callback to the mechanism. The most important things are Looper and MessagQueue, and messages that are constantly circulating.
The Handler mechanism is often mentioned and easy to confuse 20 questions are sorted out and answered for you to clarify and review
Problem preview:
- Describe the Handler mechanism in general.
- Where does Looper exist? How can thread uniqueness be guaranteed?
- How do you understand the role of ThreadLocal?
- What are the similarities and differences between Main Looper and regular Looper?
- How does Handler or Looper switch threads?
- Why doesn’t Looper’s loop() get stuck?
- How is Looper’s wait accurately awakened?
- How to get Message? Why?
- How does MessageQueue manage messages?
- Understand the similarities and differences between Message and MessageQueue?
- How are execution moments of Message managed?
- What is the relationship between Handler, Mesage, and Runnable?
- IdleHandler idle Message What’s the use?
- Asynchronous messages or synchronous barriers? How does it work? How does that work?
- Looper and MessageQueue, Message and Handler?
- What are the roles of Native side NativeMessageQueue and Looper?
- How does the Native side use Looper?
- Why does Handler cause memory leaks? How to avoid it?
- Handler is used in the system
- Why doesn’t Android allow concurrent access to the UI?
1. Describe the general mechanism of the Handler mechanism.
-
Looper prepares and starts round robin:
- Looper#
prepare()
Unique to the initialization threadLooper
As well asMessageQueue
- Looper#
loop()
openInfinite loopRead the next Message in a MessageQueue that meets the execution time- If there is no Message, call Native
pollOnce()
Enter theInfinite wait - Message exists, but execution time
when
If not, call pollOnce() and pass in the remaining time parameterLimited waiting
- If there is no Message, call Native
- Looper#
-
Message sending, joining and leaving:
- Native side if in infinite wait: any thread direction
Handler
sendMessage
或Runnable
After 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
- Native side if in infinite wait: any thread direction
-
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.
- There are
mCallback
Property to call backHandler$Callback
- Otherwise, the callback
handleMessage()
- There are
2. Where does Looper live? How can thread uniqueness be guaranteed?
- Looper instances are managed in static properties
sThreadLocal
中 ThreadLocal
Through internalThreadLocalMap
Hold the stars,key
Is the ThreadLocal instance itself,value
Is the Looper instance- Each Thread has its own ThreadLocalMap, which ensures that each Thread corresponds to a separate Looper instance
myLooper()
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:
- 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
- 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:
- Both instances are created by indirectly calling the Looper constructor with Looper#prepare()
- 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?
-
The Handler is created to specify the Looper of the thread it belongs to, which in turn holds the MessageQueue unique to the Looper
-
Looper#loop() keeps reading the appropriate Message in MessageQueue and enters the wait if there are no messages
-
When a Message or Runnable is sent to the Handler, the Message is inserted into the holding MessageQueue
-
When the Message arrives and meets the criteria, the thread to which the MessageQueue belongs wakes up and the Message is returned to Looper
-
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 status 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: Get from Message’s static method obatin(), because instead of mindlessly new, the method gets the instance from a singly linked pool and puts it back into the pool after recycle()
-
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 time
when
Attribute sequencing is managed in MessageQueue- When of the delayed Message is equal to
The current time of the invocation
anddelay
The sum of the - When for a non-delayed Message equals
The current moment
(delay for0
) - Queue-jumping Message when is fixed to
0
To facilitate insertion into the queuehead
- When of the delayed Message is equal to
- MessageQueue will then base onThe read time is compared to 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 fact
callback
Property 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:
- Activity
destroy
The callback is inIdleHandler
中 ActivityThread
中GCHandler
IdleHandler is used and executed at idle timeGCoperation
- Activity
-
An App:
- 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
- Putting part initialization in IdleHandler does not affect the start of the Activity
-
Egg problem:
add/remove
Do IdleHandler methods need to be used in pairs?
No, the callback return false can also be removed
-
Why not go into an infinite loop when mIdleHanders are never empty?
After executing the IdleHandler, the count is reset to 0 to ensure that the next iteration of the loop is not repeated
- Can some unimportant startup services be moved to IdleHandler?
Best not, callback timing is not very controllable, need to use carefully with remove
- The IdleHandle
queueIdle()
Which thread does it run on?
Depends on the thread to which IdleHandler adds MessageQueue
14. Asynchronous messages or synchronization barriers? How does it work? How does that work?
-
Asynchronous Message: Message instance with isAsync property set
- 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 on MessageQueue before consuming a Message. ** The lock will be released before waiting for a Message to be consumed. ** 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 connects to MessageQueue on the Java side, performs subsequent wait and wake, and then creates a WAKE FD and waits or wakes through 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 takes over the waiting and waking of the Java side Looper, In addition, it also provides API such as Message, MessageHandler or WeakMessageHandler, LooperCallback or SimpleLooperCallback
-
These parts can be called directly by the Native side of Looper, as InputFlinger uses Looper extensively
-
The main method is to create Looper by calling the Looper constructor or prepare, then polling via poll, followed by sendMessage or addEventFd, and 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!
Recommended reading
-
If you are interested in the details of Message creation and reuse, you can refer to how to create Message instances of Handlers, why Not Just New?
-
If you are not sure how many types of messages there are and how they have been executed behind the scenes, or if you are not familiar with the special uses of idle handlers and synchronization barriers, you are advised to read “The Use and principles of various Messages in the Swastiggle Handler”.
-
If you are curious about why Looper quit and how it works, you can refer to “Looper needs to quit manually. What about the main thread Looper?”
-
If you’re wondering about Handler memory leaks and want to clear them up once and for all, try this article: Reunderstanding Why Handlers might Leak?