preface
Android development is certainly inseparable from dealing with Handler. It is usually used as a communication tool between main thread and sub thread, and Handler, as an important member of message mechanism in Android, does bring great convenience to our development.
It can be said that as long as there is an asynchronous thread communicating with the main thread, there must be a Handler.
So, what is the principle behind the Handler's communication mechanism?
This article will show you.
Note: the system source code shown in this article is based on Android-27 and has been deleted.
1. Re recognize Handler
We can use Handler to send and process messages and Runnable associated with a thread. (Note: Runnable is encapsulated in a Message, so it is essentially a Message )
Each Handler is bound to a thread and associated with the thread's MessageQueue, so as to realize message management and inter thread communication.
1.1 basic usage of handler
android.os.Handler handler = new Handler(){ @Override public void handleMessage(final Message msg) { //Messages are accepted and processed here } }; //send message handler.sendMessage(message); handler.post(runnable);
Instantiate a Handler and override handleMessage Method, and then call its send when needed And post A series of methods can be used, which is very simple and easy to use, and supports delayed messages. (for more methods, please consult the API documentation)
But it's strange that we didn't see any MessageQueue, nor did we see its logic bound to threads. What's going on?
2. Principle analysis of handler
I believe you have heard about Looper and MessageQueue for a long time, so I won't beat around the bush.
However, before starting to analyze the principle, clarify our problem:
- How are handlers associated with threads?
- Who manages the messages sent by the Handler?
- How does the message return to the handleMessage() method?
- What's the matter with thread switching?
two point one Association between Handler and Looper
In fact, when we instantiate the Handler, the Handler will check whether the Looper of the current thread exists. If it does not exist, an exception will be reported, that is, the Looper must be created before creating the Handler.
The code is as follows:
public Handler(Callback callback, boolean async) { //Check whether the current thread has a Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //Looper holds a MessageQueue mQueue = mLooper.mQueue; }
I believe many students have encountered this exception, but we usually use it directly. We can't feel this exception because the main thread has created a Looper for us. Remember first, we'll talk about it later.
A complete example of Handler usage is as follows:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }
Looper.prepare() :
//Looper private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
Looper provides Looper.prepare() Method to create a looper, and the binding function with the current thread will be realized with the help of ThreadLocal. Looper.loop() will continue to try to obtain messages from MessageQueue and distribute them to the corresponding Handler (see [2.3]).
In other words, the association between Handler and thread is realized by Looper.
2.2 Message storage and management
The Handler provides some column methods for us to send messages, such as send() series and post() series.
However, no matter what method we call, we will eventually go to MessageQueue.enqueueMessage(Message,long) method.
With sendEmptyMessage(int) For example:
//Handler sendEmptyMessage(int) -> sendEmptyMessageDelayed(int,int) -> sendMessageAtTime(Message,long) -> enqueueMessage(MessageQueue,Message,long) -> queue.enqueueMessage(Message, long);
Here, the manager of the message MessageQueue is exposed.
Gu Ming thinks that MessageQueue is a queue, which is responsible for incoming and outgoing messages.
2.3 Message distribution and processing
After understanding the sending and storage management of Message, it is time to lift the veil of distribution and processing.
I mentioned Looper.loop() Be responsible for the distribution of messages and analyze them in this chapter.
Let's first look at the methods involved:
//Looper public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; //... for (;;) { // Keep getting messages from MessageQueue Message msg = queue.next(); // might block //Exit Looper if (msg == null) { // No message indicates that the message queue is quitting. return; } //... try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { //... } //... //Recover message, see [ 3.5 ] msg.recycleUnchecked(); } }
MessageQueue.next() is called in loop():
//MessageQueue Message next() { //... for (;;) { //... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //... if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } } // Run the idle handlers. Learn about IdleHandler yourself //... } }
Also called msg.target.dispatchMessage(msg) , Msg.target is the Handler that sent the message, so it is called back to the Handler:
//Handler public void dispatchMessage(Message msg) { //msg.callback is Runnable. If it is a post method, this if will be followed if (msg.callback != null) { handleCallback(msg); } else { //For callback, see [3.4] if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //Callback to Handler's handleMessage method handleMessage(msg); } }
Note: the dispatchMessage() method does special processing for the runnable method. If so, it will be executed directly Runnable.run() .
Analysis: loop. Loop () is an endless loop. It will continuously call MessageQueue.next() to get the Message and call msg.target.dispatchMessage(msg) Back to the Handler to distribute the Message, so as to complete the callback of the Message.
Note: the loop() method does not block the main thread. See [6].
So what about thread switching?
Many people do not understand this principle, but it is actually very simple. We draw the involved method call stack as follows:
Thread.foo(){ Looper.loop() -> MessageQueue.next() -> Message.target.dispatchMessage() -> Handler.handleMessage() }
Obviously, the thread of Handler.handleMessage() is ultimately determined by the thread calling loop. Loop ().
We usually send messages from the asynchronous thread to the Handler. The Handler's handleMessage() method is called in the main thread, so the message is switched from the asynchronous thread to the main thread.
2.3 schematic principle
The principle analysis of the text version is over here. If you still don't understand it here, it doesn't matter. I specially prepared some pictures for you. With the previous chapters, you can read them a few more times.
2.4 summary
Behind the Handler is the assistance of Looper and MessageQueue, which work together and have a clear division of labor.
Try to summarize their responsibilities as follows:
- Looper: responsible for the associated thread and Message distribution. Under this thread * * get the Message from the MessageQueue and distribute it to the Handler;
- MessageQueue: it is a queue that is responsible for Message storage and management and managing messages sent by the Handler;
- Handler: it is responsible for sending and processing messages, provides API s for developers, and hides the details of the implementation behind it.
Summarize the questions raised in chapter [2] in one sentence:
Messages sent by the Handler are stored and managed by the MessageQueue, and Loopler is responsible for callback messages to handleMessage().
The conversion of threads is completed by Looper, and the thread where handleMessage() is located is determined by the thread where Looper.loop() caller is located.
3. Extension of handler
Although the Handler is simple and easy to use, you still need to pay attention to it. In addition, there are some little-known knowledge and skills related to the Handler, such as IdleHandler.
Due to the characteristics of Handler, it is widely used in Android, such as AsyncTask, HandlerThread, Messenger, IdleHandler, IntentService and so on.
I'll explain some of these. I can search for relevant content to understand what I haven't mentioned.
3.1 causes of memory leakage caused by handler and the best solution
The Handler allows us to send a delay message. If the user closes the Activity during the delay, the Activity will be disclosed.
This disclosure is because the Message will hold the Handler, and because of the characteristics of Java, the internal class will hold the external class, so that the Activity will be held by the Handler, which will eventually lead to the disclosure of the Activity.
The most effective way to solve this problem is to define the Handler as a static internal class, hold the weak reference of the Activity internally, and remove all messages in time.
The example code is as follows:
private static class SafeHandler extends Handler { private WeakReference<HandlerActivity> ref; public SafeHandler(HandlerActivity activity) { this.ref = new WeakReference(activity); } @Override public void handleMessage(final Message msg) { HandlerActivity activity = ref.get(); if (activity != null) { activity.handleMessage(msg); } } }
And remove the message before Activity.onDestroy(), adding a layer of guarantee:
@Override protected void onDestroy() { safeHandler.removeCallbacksAndMessages(null); super.onDestroy(); }
This double guarantee can completely avoid memory leakage.
Note: simply removing messages in onDestroy is not safe because onDestroy does not necessarily execute.
3.2 why can we use Handler directly in the main thread without creating Looper?
We mentioned earlier that each Handler thread has a Looper, and the main thread is no exception, but we have not prepared the Looper of the main thread and can use it directly. Why?
Note: we usually think of ActivityThread as the main thread. In fact, it is not a thread, but the manager of the main thread operation, so I think the ActivityThread It is understandable that the main thread is the main thread. In addition, the main thread can also be called UI thread.
The ActivityThread.main() method has the following code:
//android.app.ActivityThread public static void main(String[] args) { //... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } //... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
Looper.prepareMainLooper(); The code is as follows:
/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
You can see that the Looper.prepareMainLooper() method is called in the ActivityThread, the Looper of the main thread is created, and the loop() method is called, so we can directly use the Handler.
Note: loop. Loop () It is an endless loop, and the following code will not be executed under normal conditions.
3.3 Looper of main thread is not allowed to exit
If you try to exit Looper, you will get the following error message:
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit. at android.os.MessageQueue.quit(MessageQueue.java:415) at android.os.Looper.quit(Looper.java:240)
why? In fact, the reason is very simple. The main thread is not allowed to exit, which means that the APP needs to hang up.
3.4 what can the Callback hidden in the handler do?
In the Handler's construction method, there are several requests to pass in a Callback. What is it and what can it do?
Let's take a look at Handler.dispatchMessage(msg) method:
public void dispatchMessage(Message msg) { //The callback here is Runnable if (msg.callback != null) { handleCallback(msg); } else { //If callback processes the msg and returns true, handleMessage will not be called back if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
You can see that Handler.Callback has the right to process messages first , When a message is processed and intercepted by callback (returns true), the Handler's handleMessage(msg) Method will not be called; If callback If the message is processed but not intercepted, it means that a message can be processed by callback and Handler at the same time.
This is very interesting. What does it do?
We can use the Callback interception mechanism to intercept the Handler's message!
Scene: Hook ActivityThread.mH , there are member variables mH in ActivityThread , It is a Handler and an extremely important class. Almost all plug-in frameworks use this method.
3.5 the best way to create a Message instance
Because handlers are very commonly used, Android has designed a recycling mechanism for messages to save overhead. Therefore, we try to reuse messages when using them to reduce memory consumption.
There are two methods:
- Static method via message Message.obtain(); obtain;
- Public method through Handler handler.obtainMessage(); .
3.6 correct posture of bouncing Toast in sub thread
When we try to play Toast directly in the sub thread, we will crash:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
In essence, the implementation of Toast depends on the Handler, which can be modified according to the requirements of the sub thread to use the Handler (see [2.1]). Similarly, Dialog.
The correct example code is as follows:
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Toast.makeText(HandlerActivity.this, "It won't collapse!", Toast.LENGTH_SHORT).show(); Looper.loop(); } }).start();
3.7 clever use of Looper mechanism
We can use Looper's mechanism to help us do some things:
- Send Runnable post to the main thread for execution;
- Use Looper to judge whether the current thread is the main thread.
The complete example code is as follows:
public final class MainThread { private MainThread() { } private static final Handler HANDLER = new Handler(Looper.getMainLooper()); public static void run(@NonNull Runnable runnable) { if (isMainThread()) { runnable.run(); }else{ HANDLER.post(runnable); } } public static boolean isMainThread() { return Looper.myLooper() == Looper.getMainLooper(); } }
Can save a lot of template code.
4. Summary of knowledge points
From the above, we can get some knowledge points. Summarize them for easy memory.
- Behind the Handler are looper and MessageQueue. Looper is responsible for message distribution and MessageQueue is responsible for message management;
- Before creating a Handler, you must first create a Looper;
- Looper has the function of exit, but the looper of the main thread is not allowed to exit;
- Looper of asynchronous thread needs to call looper. Myloop(). Quit(); sign out;
- Runnable is encapsulated in Message, which can be said to be a special Message;
- Handler.handleMessage() The thread in which the loop. Loop () method is called, or the thread in which the loop is located, is not the thread that creates the handler;
- Using Handler in the way of internal class may lead to memory leakage. Even if the delay message is removed in Activity.onDestroy, it must be written as a static internal class;
Article transferred from https://juejin.cn/post/6844903783139393550 , in case of infringement, please contact to delete.
Related videos:
Analysis of key questions in Android golden nine silver ten interview -- detailed analysis of Handler source code_ Beep beep beep_ bilibili
[advanced Android tutorial] - Handler source code analysis for Framework interview_ Beep beep beep_ bilibili
[advanced Android tutorial] - Analysis of hot repair Principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - how to solve OOM problems and analysis of LeakCanary principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - OkHttp principle_ Beep beep beep_ bilibili
[advanced Android tutorial] - finally, someone can explain the design principle of retrofit clearly_ Beep beep beep_ bilibili
[advanced Android tutorial] - WorkManager principle of interview core_ Beep beep beep_ bilibili