Detailed explanation of the working process of Android message mechanism handler

overview

In the Android system, for the sake of performance optimization, the UI operation for Android is not thread safe. In other words, if there are multiple threads to operate UI components, it may lead to thread safety problems. Therefore, Android stipulates that UI can only be operated in UI thread. This UI thread is opened when the application is started for the first time, also known as the main thread. This thread is specially used to operate UI components. In this UI thread, we cannot perform time-consuming operations, otherwise anr (application not responding) will occur. If we operate the UI in a child thread, the program throws an exception back to us. This is because the thread that operates the UI is checked in viewrootimpl. If the thread that operates the UI is not a main thread, an exception is thrown (except for the case where the UI component has been operated on a non UI thread before checking the thread). So at this time, if we update the UI in the child thread, we can complete this operation through the handler.

Introduction to handler usage

In development, our use of handler is basically commonplace. Here we will briefly talk about several usage examples of handler, and we will not give a demo for demonstration. Here, we only take a look at the use of handler in the latter case: after the sub thread completes the task, send a message through the handler, and then operate the UI in the main thread. Generally speaking, we will create an anonymous inner class of handler in the main thread, and then override its handlemessage method to handle our UI operations. The code is shown below.

We can also directly implement the callback interface in the handler without creating a subclass object of the handler. The handler can operate the UI by calling back the handlemessage method in the callback interface.

Then we can send the message in the child thread.

We have given three different sending methods above. Of course, we can also delay sending through sendmessagedelayed, etc. If our handler only needs to process one message, we can process it through a series of post methods.

When handling UI operations in the handler, the handler object above must be created in the main thread. If we want to create a new handler object in the child thread, we need to specify looper for the handler.

What this looper is, we will introduce it in detail below. There is still a problem with the use of handler. Because the handler we created is an anonymous internal class, it will implicitly hold an object of the external class (of course, the same is true for the internal class), which is often a time-consuming operation in the sub thread, and this thread also holds the reference of handler, so this sub thread indirectly holds the object of the external class. Let's assume that the external class is an activity. In one case, our activity has been destroyed and the child thread is still running. Because this thread holds the object of the activity, the activity cannot be recycled until the message in the handler is processed, resulting in memory leakage. If there are too many memory leaks, it will lead to oom (OUTOFMEMORY), that is, memory overflow. So is there any good solution? We can solve this problem in two ways. The first method is to kill the sub thread while destroying the activity, and remove the corresponding message from the message queue; The second scheme is that we create a static inner class inherited from handler. Because static inner classes do not hold objects of outer classes. However, at this time, we cannot access the non static member variables of the external class, and we cannot operate the UI. At this time, we need to use weak reference in this static inner class to point to the activity object. Let's take a look at the sample code.

Handler working process

Above, we briefly explained how the handler is used. Now let's take a look at how this handler works. The message mechanism of Android is mainly composed of handler, looper, messagequeue, message, etc. The handler depends on the latter three. So let's take a look at how they are connected.

Looper

When an Android application starts, a main thread, that is, UI thread, will be created. The main thread is the activitythread. There is a static main method in the activitythread. This main method is the entry point of our application. Let's take a look at the main method.

In the above code, a looper is created for the main thread through the preparemainlooper method, and loop starts the message loop. From the above code, we can guess that there should be an dead loop in the loop method, otherwise we will throw runtimeException. That is, the message loop of the main thread is not allowed to exit. Let's take a look at the looper class. First, let's look at the construction method of looper.

In this construction method, a message queue is created. And save the object of the current thread. The quitallowed parameter indicates whether to exit the message loop. However, we notice that the constructor is private, that is, we cannot manually create a looper object. Let's take a look at how to create a looper object. Then we find the following method in the looper class.

Create a new looper object here, and then save this object in ThreadLocal. When we need looper next time, we can directly take it out of this sthreadlocal. Here is a brief description of the ThreadLocal class. ThreadLocal implements local variable storage. We store the data of the current thread in ThreadLocal. If multiple variables share a ThreadLocal object, at this time, the current thread can only obtain the variables stored by the thread, not the data of other threads. In the looper class, we provide myloop to get the looper object of the current thread. It can also be seen from the above method that a thread can only create a looper object once. Then we are looking at where the prepare is used.

Prepare method: This is used to create a looper object in the child thread, which can exit the message loop in the child thread. Preparemainlooper method: we have seen this method in the main method in the activitythread above. It creates a looper for the main thread. In the looper object created by the main thread, it is set that it is not allowed to exit the message loop. And save the looper of the main thread in smainlooper. We can get the looper of the main thread through the getmainlooper method. In addition to creating a looper object in the main method of activitythread, another thing is done, that is to open the message loop through the loop method. So let's take a look at what this loop method does.

Lines 2-6: get the looper in the current thread and get the message queue from the looper. Lines 10-11: ensure that the current thread belongs to the current process and record the real token. The implementation of clearcallingidentity is in the native layer, and the specific implementation is not analyzed. Lines 14-18: take out the message from the message queue, and the loop will jump out only when the taken out message is empty. Line 27: relinquish the message to the handler. Lines 35-42: make sure that the thread is not destroyed during the call. Line 44: recycle the message. As we just guessed, there is indeed an endless loop in the loop, and the only way to exit the loop is that the message returned by the message queue is empty. Then we get the message through the next () method of the message queue. Msg.target is the handler that sends the message, and the message is handled by the handler through the dispatchmessage method in the handler. After the message processing is completed, the message is recycled. Here we can also exit the message loop through quit and quitsafe.

We can see that the exit of the message loop is actually calling the quit method of the message queue. At this time, the message retrieved from the next method of messagequeue is null. Let's take a look at this messagequeue.

MessageQueue

Messagequeue is translated into message queue, which is implemented in the form of single linked list to improve the efficiency of insertion and deletion. For messagequeue, we only look at its queue in and queue out operations. Messagequeue enqueue method.

Here we briefly talk about the method of joining the team. The message insertion process is completed on lines 13 to 36. Here, first judge whether there is a message in the message queue. If not, the currently inserted message will be taken as the queue head, and if the message queue is in the waiting state, it will wake it up. If it is inserted in the middle, it is inserted according to the time when the message was created. Messagequeue dequeue method.

Line 11: the nativepollonce method is in the native layer. If nextpolltimeoutmillis is - 1, the message queue is waiting. Lines 25-42: fetch the message according to the time we set. Lines 43 ~ 45: at this time, there is no message in the message queue. Set nextpolltimeoutmillis to - 1, and the message queue will be in the waiting state for the next cycle. Lines 48 ~ 52: exit the message queue and return null. At this time, the message loop in looper will also terminate. Finally, let's take a look at the method of exiting the message queue:

From the above, we can see that the message queue of the main thread is not allowed to be exited. And here, exit the message queue by setting mquiting to true. It also causes the message loop to exit. Here we introduce looper and messagequeue. Let's take a look at their roles in handler.

Handler

Here, let's first look at the construction method of handler.

From this construction method, we can see that a handler object cannot be created in a thread that does not create a looper. Therefore, when creating a handler in a child thread, we first need to create a looper and start a message loop to use this handler. But in the above example, we did create a new handler object in the child thread. Let's take a look at the construction method of the above example.

In this constructor, we specify a looper object for the handler. That is, in the above example, we specify the looper of the main thread in the handler created by the sub thread, which is equivalent to creating the handler object in the main thread. Let's take a look at how the handler sends messages. The sending method of handler can be divided into post and send. Let's first look at the sending method of this post.

It is obvious here that the runnable in the post parameter is converted into a message object, and then the message is sent through the send method. Let's take a look at the getpostmessage method.

Here, we also give the runnable we implemented to the callback attribute of the message object. And return the message object. Since the post is also sent by the send method, let's go all the way, and the final message sending is handled by the sendmessageattime method. Let's take a look at the sendmessageattime method.

Then look at the enqueuemessage method.

From here, we can see that sending a message through the handler is just inserting a message into the message queue created by looper. In the loop, the message is simply fetched through the loop and sent to the dispatchmessage party in the handler for message distribution processing. Let's take a look at the dispatchmessage method.

The logic is also very simple. Msg.callback is the runnable object in the post. Handlecallback is to execute the run method in runnable.

Mcallback is the callback interface we implemented. Finally, execute the overridden handlemessage in our inherited handler class. It can be seen that the priority order is post > callback > send; At this point, we have finished analyzing the working process of the whole handler. Now we want to send a message to the child thread through the main thread, and then the child thread receives and processes the message. Such an operation is easy to implement. Let's see how to achieve it.

Click the button to see the running results.

summary

Here, let's rearrange our thinking and take a look at the whole workflow of the handler. When the main thread is created, a looper is created for the main thread. When the looper is created, a message queue is created inside the looper. When creating the key handler, take out the looper of the current thread and obtain the message queue through the looper object, and then the handler sends a message in the child thread, that is, add a message to the message queue. Finally, the message is obtained through the message loop in the looper and handed over to the handler for processing.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support programming tips.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>