Overview
Let’s discuss the relationship between the Handler mechanism and the previous relationship between Looper, Handler and Message from the following six questions?
1.How many Handlers does a thread have?
2. How many Loopers does a thread have? How to ensure it?
The reason for memory leaks? Why have other internal classes not mentioned this problem?
4. Why can the main thread new Handler? What preparations do you need to do if you want to new Handler in the child thread?
5. What is the solution for Looper maintained in the child thread when there is no message in the message queue? What is the use?
Why doesn't the dead loop cause the application to get stuck?
1. Source code analysis
For Looper, prepare() and loop() methods are mainly two methods
First look at the prepare() method
private static void prepare(boolean quitAllowed) { if (() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } (new Looper(quitAllowed)); }
It can be seen that sThreadLocal is a ThreadLocal object. ThreadLocal is not a thread, but a storage class inside a thread, which can store data in the thread. You can see on line 5 that a Looper instance is put in
ThreadLocal, and in lines 2 to 4 determine whether sThreadLocal is empty, otherwise an exception will be thrown. This also cannot be called twice. This also corresponds to the second question above.
Let’s take a look at the construction method of Looper:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = (); }
A MessageQueue (message queue) is created in the Looper constructor method
Then we are looking at the loop() method:
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; () wasn't called on this thread."); } final MessageQueue queue = ; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. (); final long ident = (); // Allow overriding a threshold with a system prop. . // adb shell 'setprop . 1 && stop && start' final int thresholdOverride = ("." + () + "." + ().getName() + ".slow", 0); boolean slowDeliveryDetected = false; for (;;) { Message msg = (); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = ; if (logging != null) { (">>>>> Dispatching to " + + " " + + ": " + ); } final long traceTag = ; long slowDispatchThresholdMs = ; long slowDeliveryThresholdMs = ; if (thresholdOverride > 0) { slowDispatchThresholdMs = thresholdOverride; slowDeliveryThresholdMs = thresholdOverride; } final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && ( > 0); final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); final boolean needStartTime = logSlowDelivery || logSlowDispatch; final boolean needEndTime = logSlowDispatch; if (traceTag != 0 && (traceTag)) { (traceTag, (msg)); } final long dispatchStart = needStartTime ? () : 0; final long dispatchEnd; try { (msg); dispatchEnd = needEndTime ? () : 0; } finally { if (traceTag != 0) { (traceTag); } } if (logSlowDelivery) { if (slowDeliveryDetected) { if ((dispatchStart - ) <= 10) { (TAG, "Drained"); slowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, , dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. slowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); } if (logging != null) { ("<<<<< Finished to " + + " " + ); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = (); if (ident != newIdent) { (TAG, "Thread identity changed from 0x" + (ident) + " to 0x" + (newIdent) + " while dispatching to " + ().getName() + " " + + " what=" + ); } (); } }
Line 2: final Looper me = myLooper();
public static @Nullable Looper myLooper() { return (); }
Line 6: Get the mQueue (message queue) in the modified Looper instance
Lines 23~98: Entering a dead loop,
Line 24: Message msg = (); next() method will keep getting messages, then locking, and the process will be blocked. This is why the Looper dead loop does not cause a crash. In this next() source code, it will be not pasted, and later it will be discussed why this will not crash.
Line 57: Call (msg); hand the message to the dispatchMessage() method of msg's target to process. What is the target of msg? It is actually a handler object, which will be analyzed below.
Line 97: Release the resource occupied by the message
The main functions of Looper:
Bind with the current thread, ensuring that a thread will have only one Looper instance, and a Looper instance will have only one MessageQueue.
The loop() method continuously retrieves messages from MessageQueue and gives them to dispatchMessage() of the target attribute of the message to process.
Before using Handler, we initialize an instance, for example, to update the UI thread. We will initialize it directly when declaring it, or initialize the Handler instance in onCreate. So let's first look at the Handler construction method.
See how it is connected to MessageQueue, how the messages sent in its child thread (usually the messages sent are in non-UI threads) are sent to MessageQueue.
public Handler(Callback callback) { this(callback, false); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((() || () || ()) && (() & ) == 0) { (TAG, "The following Handler class should be static or leaks might occur: " + ()); } } mLooper = (); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + () + " that has not called ()"); } mQueue = ; mCallback = callback; mAsynchronous = async; }
Line 15: The Looper instance saved by the current thread is obtained through(), and then the MessageQueue saved in this Looper instance is obtained in line 19.
This ensures that the handler instance is associated with the MessageQueue in our Looper instance.
Then let's look at the most commonly used sendMessage method:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = (); = what; return sendMessageDelayed(msg, delayMillis); }
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, () + delayMillis); }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); ("Looper", (), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Finally, we found that the sendMessageAtTime was called. In this method, we directly get the MessageQueue and then call the enqueueMessage method. Let's look at this method again:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { = this; if (mAsynchronous) { (true); } return (msg, uptimeMillis); }
The enqueueMessage first assigns this value. In the loop() method of Looper, each msg will be taken out and handed over to msg, (msg) to process the message, that is, the current Handler will be used as the
The target attribute of msg will eventually call the queue's enqueueMessage method, which means that the Handler sends a hungry message and will eventually be saved to the message queue.
It is now very clear: Looper will call the Prepare() and loop() methods, save a Looper instance in the currently executed thread, this instance will save a MessageQueue object, and then enter a
Go in an infinite loop and continuously read the message sent by the Handler from the MessageQueue. Then, in the callback, the dispatchMessage() method of the handler that creates this message. Let's take a look at the dispatchMessage method:
public void dispatchMessage(Message msg) { if ( != null) { handleCallback(msg); } else { if (mCallback != null) { if ((msg)) { return; } } handleMessage(msg); } }
Line 10: The handleMessage() method is called, let's look at this method below:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
You can see that this is an empty method. Why? Because the final callback of the message is controlled by us, when we create the handler, we rewrite the handleMessage method and then process the message accordingly.
For example:
private Handler mHandler = new Handler() { public void handleMessage( msg) { switch () { case value: break; default: break; } }; };
The whole process has been finished, let’s summarize:
1. First, the Looper and prepare() method saves a Looper instance in this thread, and then saves a MessageQueue object in the instance; because () can only be called once in a thread,
So MessageQueue only exists one in a thread.
() will let the current thread enter an infinite loop, constantly read messages from the MessageQueue instance, and then call back, (msg) method.
The constructor will first obtain the Looper instance saved in the current thread, and then be associated with the MessageQueue of the Looper instance.
The sendMessage() method will assign the msg target to the handler itself, and then add it to the MessageQueue.
5. When constructing a Handler instance, we will override the handlerMessage method. That is, the final call method of dispatchMessage(msg).
Let's look back at our previous six questions:
2. Analyze the problem
1.How many Handlers does a thread have?
I believe everyone should have used Handler, so the answer to this question: multiple
There is nothing to analyze about this problem, and everyone has used it personally!
2. How many Loopers does a thread have? How to ensure it?
A thread can have multiple handlers, so how many Loopers will be generated? Answer: 1
Why? How to guarantee it?
In source code analysis, you can see that sTheadLocal will instance a Looper. If the method is called again in the same thread, an exception will be thrown: Only one Looper may be created per thread
It shows that the same thread can only instance Looper objects.
Cause of memory leak?
Why have other internal classes not mentioned this issue?
The reason for Handler memory leak? Answer: Internal class refers to external class methods
private Handler mHandler =new Handler(){ @Override public void handleMessage(Message msg) { (msg); switch (){ case 0: setLog(); break; default: break; } } }; private void setLog() { (TAG,"This is Log!"); } @Override public void onClick(View v) { switch (()){ case .create_xml: (TAG,"create_xml"); (0,1000*60); break; default: break; }
Create an anonymous internal class Handler. At this time, I send a delay sendMessageDelayed() to execute the setLog() method. However, if I forcefully close the Activity, the Activity will be destroyed at this time, but this Handler cannot get it.
Release, because it will take one minute to delay the setLog() method, which will cause memory leakage.
Why don't other internal classes?
It's very simple. For example, the commonly used anonymous internal class of ListView, if the main activity is destroyed, the internal class of ViewHolder will be directly destroyed at this time! Therefore, there will be no memory leak problem!
4. Why can the main thread new Handler?
What preparations should be made for new Handler in child threads?
From the previous explanation, we can see that the condition of new Handler is that a Looper object is required, and the Looper object needs to call two methods prepare() and loop() methods. You can see the Main method of the main thread below.
public static void main(String[] args) { (Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); // Install selective syscall interception (); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. (false); (); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = (()); (configDir); Process.setArgV0("<pre-initialized>"); (); // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. // It will be in the format "seq=114" long startSeq = 0; if (args != null) { for (int i = - 1; i >= 0; --i) { if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = ( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); (false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = (); } if (false) { ().setMessageLogging(new LogPrinter(, "ActivityThread")); } // End of event ActivityThreadMain. (Trace.TRACE_TAG_ACTIVITY_MANAGER); (); throw new RuntimeException("Main thread loop unexpectedly exited"); }
This Main method is the main method that must be taken before all programs start
Line 20: A () was called;
Line 47: A () is called;
And () source code:
public static void prepareMainLooper() { prepare(false); synchronized () { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Line 2: You can see that the prepare() method in Looper is called;
So you can directly new Handler in a main thread
So if you are in a child thread new Handler, what preparations need to be made?
Of course, it is necessary to call a() and () method.
5. What is the solution for Looper maintained in the child thread when there is no message in the message queue? What is the use?
When the child thread uses Handler, call the() method. In the above source code, you can see that [Message msg = (); // might block] will be stuck in this place all the time? So how do we solve this problem?
There is a QuitSafely() method in the Looper method, which will kill all messages in the MessageQueue (message queue) and release memory and threads.
Back to the fourth question at this time, what do you need to prepare for creating a Handler in a child thread?
Call three methods:
- ()
- ()
- ().quit();
Why doesn't the dead loop cause the application to get stuck?
To understand this problem, we must first understand what circumstances will the application be stuck?
If it is stuck, the application will be unresponsive, which is what we often call ANR. There are two types of ANR problems:
- No response to input events within 5 seconds, such as: pressing the button, touching the screen
- BroadcastReceiver not completed within 10 seconds
After understanding this, we will find that the problem that leads to the Looper dead loop is the Message msg = () method. After reading the next() source code, it can be simply said that this program is sleeping, so calling the Wake() method in the next() method can wake up the program, so that it will not cause ANR problems in the application.
The above is a detailed explanation of the relationship between the Android Handler mechanism and the Looper Handler Message. For more information about the relationship between the Android Handler mechanism and the Looper Handler Message, please follow my other related articles!