Preface As we all know, Android's UI is refreshed in its main thread, so Google recommends that developers do not perform time-consuming operations in the main thread, otherwise it is easy to cause application unresponsiveness (ANR). Given this almost rigid requirement, we often put time-consuming operations (such as network requests) in sub-threads; but sub-threads cannot directly access the UI. At this point, the contradiction becomes apparent:
Well, is there anything that can reconcile and resolve this contradiction? Of course there is. Google uses Handler to cleverly connect the main thread and the child thread - the child thread performs time-consuming business logic, and then uses Handler to notify the main thread to refresh the UI. In addition, are there other ways to achieve similar operations? The answer is yes, we can also use AsyncTask or IntentService to perform asynchronous operations. How do these two do it? In fact, Handler is also used inside AsyncTask and IntentService to implement its main functions. Putting these two aside, when we open the Android source code, we can see Handler everywhere. Therefore, Handler is the core and essence of Android asynchronous operation, and it plays an extremely important and even irreplaceable role in many fields. Here, the working principle and implementation mechanism of Handler are systematically sorted out. Introduction to ThreadLocal and its use Everyone is familiar with Thread, but ThreadLocal may be a lot more unfamiliar. Although we don't know much about it, it has been released as early as JDK1.2 and has been widely used. For example, hibernate, EventBus, and Handler all use ThreadLocal for thread-related operations. If you simply look at the name ThreadLocal, it has a strong flavor of "local thread"; however, after taking a sip, you will find that it is not the flavor at all. In fact, ThreadLocal is not used to operate any local threads but to implement data copies of different threads. When using ThreadLocal to maintain variables, it will provide an independent copy of the variable for each thread that uses the variable; each thread can independently change its own copy and will not affect the corresponding copies held by other threads. Therefore, the actual role of ThreadLocal does not match the meaning implied by its name. Perhaps it would be more appropriate to change it to ThreadLocalVariable (thread local variable). Next, let's take a look at how to use ThreadLocal through an example
View the output:
In this code, ThreadLocal is used to save String type data, and different values are set for ThreadLocal in the main thread and two child threads, and then these values are retrieved separately. Combined with the output log, it can be found that the same ThreadLocal object is accessed in different threads, but the values obtained through mThreadLocal.get() are different; that is to say, there is no mutual influence between them but they remain independent of each other. After understanding this feature of ThreadLocal, it will be much easier for us to understand the working mechanism of Looper. The relationship between Looper, thread, and message queue Google officially recommends developers to use Handler to implement asynchronous UI refresh. We have also adopted this suggestion in our daily work: first, create a Handler in the main thread, then use handler.sendMessage(message) in the child thread to send a message to the main thread, and finally the message is processed in handleMessage(Message msg) {}. This routine is familiar to everyone; now let's try to create a Handler in the child thread from a different angle.
The code here is very simple: LooperThread inherits from Thread and creates a new Handler in its run() method. Uh-huh, run it again, oops, an error message:
Hmm, it's a bit of a bad start. I made an error at the very beginning... It's okay. Life is full of setbacks and hopes. This little thing doesn't count. Since the error is reported when calling the Handler constructor, let's start with the source code of the constructor and find out:
Please note the code on line 20: If mLooper == null, the system will throw the error: Can't create handler inside thread that has not called Looper.prepare(). This means: If you create a handler inside a thread, you must call Looper.prepare(). Since this prompt has already told us what to do, let's add this line of code:
Hey, it doesn't report an error anymore. Run it: Now that Looper.prepare() solves this problem, let's take a look at what is done in this method:
From this source code and its comment documents we can see: In prepare(), use a Looper to initialize the current thread or initialize a thread with a Looper. Please pay attention to line 14, which is the core of this source code. Now let's analyze it in detail:
There are two operations performed in this line of code (1) Constructing Looper
In the construction method of Looper, a message queue MessageQueue and a thread Thread are initialized. From this, we can see that a Looper corresponds to a message queue and the current thread. When a message is received, the system will store it in the message queue and wait for processing. As for Looper, it is responsible for message polling in Android's message mechanism. It will continuously check whether there are new unprocessed messages in the MessageQueue; if there are, it will be processed immediately, otherwise it will enter blocking. (2) Save this Looper to sThreadLocal. Here, sThreadLocal is a ThreadLocal type variable defined in the Looper class.
Looper is a class in the framework, and sThreadLocal is a static final variable of it. When Looper.prepare() is executed in a thread, the system will save the Looper corresponding to the thread into sThreadLocal. Different threads have different Loopers, but they are all saved in sThreadLocal by the system and do not affect each other and are independent of each other; and the Looper corresponding to different threads can be obtained through sThreadLocal.get(). After calling the prepare() method, you need to call the loop() method to start message polling, and call the quit() method to stop message polling when necessary. If Looper.prepare() is executed again, the system finds that the value of sThreadLocal.get() is no longer null and throws an exception: Only one Looper may be created per thread. Only one Looper can be created per thread! summary:
Therefore, the way to use Handler in a child thread should be like this:
Seeing this example, some people may wonder: Why don't we call Looper.prepare() and get no error when we use Handler in MainActivity? This is because the UI thread is the main thread, and the system will automatically call the Looper.prepareMainLooper() method to create the main thread's Looper and message queue MessageQueue Message sending and processing process After discussing the relationship between Looper, threads, and message queues, let's take a look at the sending and processing of Messages in the Android message mechanism. The most commonly used method: handler.sendMessage(message)——>send message handleMessage(Message msg){}——>Process the message Let's first analyze the message enqueue. Handler can send messages through post(), postAtTime(), postDelayed(), postAtFrontOfQueue() and other methods. Except postAtFrontOfQueue(), these methods will execute the sendMessageAtTime(Message msg, long uptimeMillis) method. The source code is as follows:
Here you can see that sendMessageAtTime() calls enqueueMessage() again. The important operations in this method are:
The target is set for msg. Please refer to line 25 of the code. Here, this is the current Handler object itself. This indicates the source of the msg - which Handler sent it, and at the same time, it also indicates the destination of the msg - which Handler should handle it. It is not difficult to find that the Handler that sent the message is responsible for handling it.
Put the message into the message queue. Please refer to line 29 of the code. In enqueueMessage(msg,uptimeMillis), the message Message is stored in the message queue. The message with the shortest time from the trigger is at the front of the queue. Similarly, the message with the longest time from the trigger is at the end of the queue. If the sendMessageAtFrontOfQueue() method is called to send a message, it will directly call enqueueMessage(msg,uptimeMillis) to enqueue the message, but the delay time is 0, which means that the message will be inserted into the head of the message queue and executed first. Intuition tells us that the message queue mQueue here is the message queue corresponding to the thread. But intuition alone is not enough and may even be unreliable. Let's look back at the construction method of Handler and find the exact basis from the source code.
(1) Get Looper, see line 10 of the code (2) Use Looper's message queue to assign a value to mQueue. See line 15 of the code. (3) Assign a value to mCallback, see line 16 of the code (4) Assign a value to mAsynchronous, see line 17 of the code Well, you see, this mQueue is taken from Looper. Previously, we also analyzed in detail the one-to-one correspondence between Looper, thread, and message queue, so the mQueue here is the message queue corresponding to the thread. After reading about the message entry, let's analyze the message exit. Please see the loop() method source code in Looper:
We found that the message processing is done in an infinite loop, see lines 13-55 of the code. That is to say, in this code, the Looper is always polling the message queue MessageQueue. If there is no unprocessed message in the message queue (i.e. queue.next()==null), it enters the blocking state. If there is a message to be processed in the message queue (i.e. queue.next()!=null), it uses msg.target.dispatchMessage(msg) to dispatch the message to the corresponding Handler. At this point, some people may have a question: How does the system know which Handler to send the message to? Hey, do you remember that in enqueueMessage(), the system sets a target for msg to determine its target Handler? Well, so as long as msg.target.dispatchMessage(msg) is used, the message can be dispatched to the corresponding Handler. So what operations will be performed on the message in dispatchMessage()? Let's continue to follow the source code
Wow, seeing this, I feel much better. Basically, I am back to the place we are familiar with. Here, the Message message is processed. Let's take a look at the main steps.
Process the Message callback, see line 3 of the code For example, when calling handler.post(Runnable runnable), the runnable will be encapsulated by the system as a Message callback. This is also reflected very intuitively in the source code:
Process the Handler callback, see line 6 of the code For example, when Handler handler = Handler (Callback callback) is executed, callback will be assigned to mCallback. This has been analyzed when introducing the Handler construction method, so I will not repeat it here. Step 3: Call handleMessage() to process the message Message, see line 10 of the code The source code of handleMessage() is as follows:
Well, it is an empty method. So the Handler subclass needs to override this method and process the received message in it. Sorting out the Handler working mechanism So far, the asynchronous mechanism of Handler and its implementation principle have been analyzed. Here, we will make a comprehensive review and summary. The Android asynchronous message mechanism mainly involves: Thread, Handler, MessageQueue, Looper. They play different roles and bear different responsibilities in the entire mechanism.
The operations in a thread are determined by their respective business logic and are performed according to the specific circumstances.
The usual practice is to create a Handler in the main thread and use it to send messages to the main thread in the child thread. After the main thread receives the message, it will process it.
All messages sent by the Handler will be saved in the message queue MessageQueue. The system will determine the position of the message in the queue based on the length of time from the message to the trigger. The messages in the queue will be dequeued in turn and processed accordingly.
Looper uses its loop() method to poll the message queue and dispatches the message to the corresponding Handler when it is dequeued. To better understand the relationship and role of these, please refer to the following diagram: Wrong posture of using Handler and its potential risks The specific usage of Handler, especially the conventional usage, will not be listed here one by one. |
<<: A collection of ViewHolder tool classes
Coolpad is a powerful company, and China Cool All...
2020 is going to be tough for phone makers, with ...
The Yunnan tree shrew Tupaia belangeri chinensis ...
Chicken bouillon and MSG For some people (such as...
Recently, the topic of "Hex drinks increase ...
When you are stuck in traffic, you often have thi...
[[164431]] original Preface Before you begin, it ...
The core position of the "content is king&qu...
Toyota is a national brand of Japan and a world-r...
Few people can associate smog with diamonds, but ...
Qingming Festival is coming In the blink of an ey...
Bird statue The bird is the most obvious shape in...
"Corn silk water can 'diuretic and blood...
Source code introduction For NFC models, you can ...
On the evening of November 29th, Beijing time, th...