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 all know that we need to start the Looper thread by ourselves and manually call quit() or quitSafely() to stop the Looper round at the end of the task. But for the details seem not to have thought carefully, take five minutes to learn briefly!
- Why did Looper manually quit?
- How to deal with Message when quit?
- What are the optimizations for the quitSafely?
- Does the main thread Looper need quit?
Why should the Looper thread quit manually?
The thread that creates the Looper and executes loop() needs to manually call quit at the end of the task.
Otherwise, the thread will remain runnable due to loop() polling and CPU resources will not be released. It is more likely to cause a memory leak because Thread is holding out-of-life instances as GC Root.
When quit is called, Looper no longer waits because there is no Message, but instead fetches tonull
, which triggers the exit of the round robin loop.
// Looper.java
public static void loop(a) {...for (;;) {
Message msg = queue.next();
if (msg == null) {
// Exit with null
return; }... }}Copy the code
How to deal with Message when quit?
Much of Looper’s processing is actually done by MessageQueue, including Looper#quit() here. It actually calls the MessageQueue function of the same name, quit(Boolean), and specifies the safe parameter as false.
// Looper.java
public void quit(a) {
// The default is an unsafe exit
mQueue.quit(false);
}
Copy the code
MessageQueue#quit() performs a few simple tasks, including marking the exit, emptying all outstanding mesages, and finally waking up the thread.
// MessageQueue.java
void quit(boolean safe) {...synchronized (this) {... mQuitting =true; / / tag quitting
// An unsafe exit will reclaim all messages in the queue and empty the queue
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// Wake up the threadnativeWake(mPtr); }}Copy the code
- Exit flags will result in subsequent
sendMessage()
或postRunnable()
Invalid, return directlyfalse
.
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {...synchronized (this) {...if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
// Contract/formspapers flag Causes subsequent Message send failures
return false; }... }return true;
}
Copy the code
- The default policy is to empty the queue of all messages, including messages that arrive at exactly the right time
friendlysecurity.
// MessageQueue.java
// All in one: leave the queue whether or not when arrives
// Messages that should be executed at the current moment may also be culled and cannot be executed
private void removeAllMessagesLocked(a) {
Message p = mMessages;
while(p ! =null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
Copy the code
- Finally, the thread wakes up and enters the next loop to read the queue, which returns NULL because there is no Message in the queue.
// MessageQueue.java
Message next(a) {...for(;;) {...synchronized (this) {...// There is no Message in the queue
// Will return NULL directly for the presence of the row exit flag
if (mQuitting) {
dispose();
return null; }... }... }}Copy the code
- Loop () gets null Message, the loop exits, and the thread terminates.
What are the optimizations for the quitSafely?
It is well known that the SDK recommends using the quitSafely() to terminate Looper because it will only discard messages whose execution time is later than the current call time.
This ensures that the messages that meet the execution time condition remain in the queue when the quitSafely is called and exit the polling only after they are all executed.
- Call MessageQueue#quit() and specify the safe parameter as
true
.
// Looper.java
public void quitSafely(a) {
mQueue.quit(true);
}
Copy the code
- When the security quit is called
removeAllFutureMessagesLocked()
.
// MessageQueue.java
void quit(boolean safe) {
synchronized (this) {
if (safe) {
// Safely exit the call
removeAllFutureMessagesLocked();
} else{ removeAllMessagesLocked(); } nativeWake(mPtr); }}Copy the code
As the name suggests, it simply removes future messages. Compared to the present moment, not all things that have not been implemented are called the future, don’t get me wrong!
// MessageQueue.java
private void removeAllFutureMessagesLocked(a) {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if(p ! =null) {
// If the execution time of the queue header Message is still later than the current time, then all are cleared
if (p.when > now) {
removeAllMessagesLocked();
} else {
// Otherwise, the queue is traversed and messages to be culled are filtered
Message n;
for (;;) {
n = p.next;
// If there is no Message later than that, it does not need to be removed
if (n == null) {
return;
}
// Find the last Message in the queue that was later than the current time
if (n.when > now) {
break;
}
p = n;
}
// All messages after the first Message are queued
p.next = null;
// Reclaim the last Message that was later than the current moment and subsequent messages
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while(n ! =null); }}}Copy the code
- The next() loop that wakes up the thread after removing future messages retrieves any messages left in the queue for processing.
Message next(a) {...for(;;) {...synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null; Message msg = mMessages; .if(msg ! =null) {
// Message in queue earlier than current time, else
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// Message exits the queue and updates the pointer
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
// The queue leader moves back one node
mMessages = msg.next;
}
// Get the Message and pass it to Looper for callback
msg.next = null;
msg.markInUse();
returnmsg; }}else{... }// There are still messages in the queue
// The exit flag will not return null for now
if (mQuitting) {
dispose();
return null; }}... }}Copy the code
- When all the remaining messages are executed, next() will not fetch Message, and eventually because
quitting
Flag returns null, which triggers the exit of loop().
Does the main thread Looper need quit?
The main thread ActivityThread creates Looper with a flag that disallows quit, that is, quit cannot be called manually.
// Looper.java
public static void prepareMainLooper(a) {
prepare(false); . }private static void prepare(boolean quitAllowed) {... sThreadLocal.set(new Looper(quitAllowed));
}
// Main Looper is initialized with a disallowed exit
private Looper(boolean quitAllowed) {
mQueue = newMessageQueue(quitAllowed); . }Copy the code
If quit() is forcibly called in the main thread, the following exception occurs:
java.lang.IllegalStateException: Main thread not allowed to quit.
// MessageQueue.java
void quit(boolean safe) {
if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit."); }... }Copy the code
-
Does the main thread need quit?
No, App is recycled directly by AMS when memory is low.
-
The reason why you don’t need quit is that?
The main thread is important for managing the life cycle of components such as ContentProviders, activities, and Services. Even when one component dies, it still exists to schedule other components.
In other words, the ActivityThread is scoped beyond these components and should not be handled by them. For example, if an Activity destroys, the ActivityThread still handles transactions for other activities or services and other components, and cannot terminate.
conclusion
For the purpose of reclaiming resources or avoiding memory leaks, you should manually quit after Thread completes the task. To make sure that any Message that could have been executed safely when quit is executed, call quitSafely. Also understand the importance of the main thread Looper, it does not need and cannot be manually quit!