preface
Handler belongs to a very classic question in the eight-part essay, resulting in this knowledge point a lot of times, the examiner is too lazy to ask; I’ve seen this for a long time, but after a while, it’s easy to forget, but dealing with memory leaks, IdleHandler, etc. Although the examiner often doesn’t bother to ask, it’s awkward if you forget and don’t know how to answer
I also come to fry leftovers, and strive to be easy to understand to describe the whole process of the Handler mechanism; Related knowledge points, draw some flow charts, sequence diagram to show its operation mechanism, and strive to make this article illustrated!
Key method source in the article, you can directly click the method name, jump to view the corresponding method source
If you don’t get anything, spray me!
The total process
To get a general idea of what this handler is doing, here’s a flowchart
As can be seen from the above flow chart, it is generally divided into several large blocks
- Looper.prepare(), Handler(), and looper.loop () total process
- To send and receive messages
- Distribution of the message
Relevant knowledge point involves these probably, explain in detail below!
- To view the mind map in detail, please right-click to download and view it
use
First look at the use, otherwise the source code, schematic diagram made a lot of, can not remember how to use, it is embarrassing
Use is very simple, here is only a demonstration, we can be familiar with the next
The demo code is as simple as possible to demonstrate, but I won’t show you anything about static inner classes holding weak references or emptying message queues in destruction callbacks
- Let’s look at the distribution method for message processing: dispatchMessage(MSG)
Handler.java
...
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}...Copy the code
In general, the use of handler is divided into two categories, subdivided into three small classes
- Sending and receiving integrated
- handleCallback(msg)
- Split sending and receiving messages
- mCallback.handleMessage(msg)
- handleMessage(msg)
Send and receive an
- handleCallback(msg)
- With post, both sending and receiving are integrated and are done in the POST () method. There is no need to create a Message instance, etc. The POST method already does that
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
// Send and receive messages
new Thread(new Runnable() {
@Override public void run(a) {
String info = The first way;
mHandler.post(new Runnable() {
@Override public void run(a) { msgTv.setText(info); }}); } }).start(); }}Copy the code
To send and receive separate
mCallback.handleMessage(msg)
- Implement Callback interface
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler(new Handler.Callback() {
// Receive the message and refresh the UI
@Override public boolean handleMessage(@NonNull Message msg) {
if (msg.what == 1) {
msgTv.setText(msg.obj.toString());
}
// False overrides the handleMessage of the Handler class. True is not called
return false; }});@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
// Send a message
new Thread(new Runnable() {
@Override public void run(a) {
Message message = Message.obtain();
message.what = 1;
message.obj = "The second way -- 1"; mHandler.sendMessage(message); } }).start(); }}Copy the code
handleMessage(msg)
- Override the handlerMessage(MSG) method of the Handler class
public class MainActivity extends AppCompatActivity {
private TextView msgTv;
private Handler mHandler = new Handler() {
// Receive the message and refresh the UI
@Override public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 1) { msgTv.setText(msg.obj.toString()); }}};@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgTv = findViewById(R.id.tv_msg);
// Send a message
new Thread(new Runnable() {
@Override public void run(a) {
Message message = Message.obtain();
message.what = 1;
message.obj = "Second way -- 2"; mHandler.sendMessage(message); } }).start(); }}Copy the code
Prepare and loop
I’m sure you’re under the impression that when you’re communicating with a child thread, you have to initialize the Handler in the child thread, so you have to write this
- Prepare before, loop after, solidify impression
new Thread(new Runnable() {
@Override public void run(a) {
Looper.prepare();
Handler handler = newHandler(); Looper.loop(); }});Copy the code
- Why doesn’t the main thread need to be written like this? You must have thought about it, and you must have done something like this at the exit
ActivityThread.java
...
public static void main(String[] args) {...// main thread Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// The main thread loop starts the loopLooper.loop(); . }...Copy the code
Why use prepare and loop? I’ve drawn a picture, just to give you an idea
- The process of the above picture, I feel the overall painting is relatively clear
- So to summarize this
- Looper.prepare() : Generates a Looper object and sets it in a ThreadLocal
- Handler constructor: Gets the Looper object of ThreadLocal via looper.mylooper ()
- Looper.loop() : There is an inner loop that starts event distribution; It is also the most complicated and most productive method
Take a look at each step of the source code, here will also be calibrated link, convenient for you to view at any time in the past
- Looper.prepare()
- Prepare () can only be used once in a thread, otherwise an exception will be raised
Looper.java
...
public static void prepare(a) {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if(sThreadLocal.get() ! =null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(newLooper(quitAllowed)); }...Copy the code
- Handler()
- MyLooper () –> sthreadLocal.get () to get the Looper instance
Handler.java
...
@Deprecated
public Handler(a) {
this(null.false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) = =0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }...Copy the code
Looper.java
...
public static @Nullable Looper myLooper(a) {
returnsThreadLocal.get(); }...Copy the code
- Looper.loop(): The method is analyzed in
Distribution of the message
in- Simplified a large number of source code, detailed can click above method name
- Message MSG = queue.next() : iterates over the Message
- MSG. Target. DispatchMessage (MSG) : distribution of the message
- Msg.recycleunchecked () : Message recycling, entering the message pool
Looper.java
...
public static void loop(a) {
finalLooper me = myLooper(); .finalMessageQueue queue = me.mQueue; .for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; }...try {
msg.target.dispatchMessage(msg);
if(observer ! =null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if(observer ! =null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked(); }}...Copy the code
To send and receive messages
The interface for sending and receiving messages is in Handler, which is the most intuitive point of contact for us
The mind map below summarizes the whole
Front knowledge
Before we talk about sending and receiving messages, it’s important to explain an important property in Message: when
The variable when is in Message, and we don’t set it when sending messages, and we can’t set it, because only the inner package can access the write. This property is set to the sent message when the message is enqueued to the message queue. Method for enqueueMessage(…)
When we use sendMessage to send messages, we will actually call sendMessageDelayed to delay sending messages, but at this time, the incoming delay time will default to 0. Take a look at the delay method: sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code
This is where the sendMessageAtTime method is called. Here! Systemclock. uptimeMillis() + delayMillis
- Systemclock. uptimeMillis() : This method returns a number of milliseconds, which is the number of milliseconds it took to turn the device on.
- DelayMillis: That’s the millisecond delay we send
This time is later assigned to when: when = systemclock. uptimeMillis() + delayMillis
When refers to a moment in the present or future in real time (actually, when is a relative moment, and the relative point is the starting point).
Send a message
There are two ways to send messages: POST (…) And sendMessage (…).
- Post (Runnable) : Both sending and receiving messages are done in post
- SendMessage (MSG) : You need to pass in the Message object yourself
- Look at the source
- Using POST automatically creates a Message object through the getPostMessage method
- EnqueueMessage adds the generated Message to the Message queue, notice
- This method assigns the current handler to the MSG target before adding the message to the message queue
- MSG. SetAsynchronous (true) : Set Message to asynchronous, default to synchronous; The condition for asynchron needs to be set manually in the Handler constructor
Handler.java
...
//post
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// Generate a Message object
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
/ / sendMessage method
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
/// Add Message to the detail queue
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
/ / set the target
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
// Set it to asynchronous
msg.setAsynchronous(true);
}
returnqueue.enqueueMessage(msg, uptimeMillis); }...Copy the code
- enqueueMessage(…): Simplified some code, complete code, can click on the left method name
- Message joins the Message queue via enqueueMessage
- When = systemclock. uptimeMillis() + delayMillis, when represents a time scale, the time scale is arranged in order of the message queue, that is, the message queue: Line up from now to the future
- Note that mMessage is the message location to which the current message is distributed
- MMessage is empty, MSG passed in is the header of the message chain, and next is empty
- If mMessage is not empty and there are no delayed messages in the message queue: move from the current distribution location to the end of the list, insert the incoming MSG to the end of the list, and empty next
- The case where mMessage is not empty and contains delayed messages: an example
- Messages of A, B and C are sent in sequence, and the edge delay of the three is 3 seconds, 1 second, 2 seconds {A(3000), B(1000), C(2000)}
- This is an ideal situation: all three enter in sequence, with negligible time differences between them, for demonstration and illustration purposes
- This sequential ordering by distance ensures that messages without delay or with a small delay time can be executed in a timely manner
- The order in the message queue is B –> C –> A
MessageQueue.java
...
boolean enqueueMessage(Message msg, long when) {...synchronized (this) {... msg.markInUse(); msg.when = when; Message p = mMessages;boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr ! = 0 because mQuitting is false.
if(needWake) { nativeWake(mPtr); }}return true; }...Copy the code
- Take a look at the diagram of the sent message inserted into the message queue
Receives the message
Receiving messages is relatively easy
- DispatchMessage (MSG) : Key method
Handler.java
...
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}...Copy the code
-
handleCallback(msg)
- Trigger condition: The handleCallback callback is implemented in the Message Message
- Post () ¶ setCallback(Runnable r) is shown as @unsupportedappusage, is hidden, cannot be called, can be called with reflection, but not necessary…
-
mCallback.handleMessage(msg)
- The trigger condition
- Send messages using the sendMessage method (required)
- Implement the Handler Callback
- The distributed message is distributed in a callback implemented in the Handler
- The trigger condition
-
handleMessage(msg)
- The trigger condition
- Send messages using the sendMessage method (required)
- The Handler Callback is not implemented
- Handler Callback (McAllback.handlemessage (MSG))
- You need to override the handlerMessage method of the Handler class
- The trigger condition
Distribution of the message
Message distribution is done in loop(), so let’s take a look at loop() as an important method
- Looper.loop(): simplified a huge amount of source code, detailed can click on the left method name
- Message MSG = queue.next() : iterates over the Message
- MSG. Target. DispatchMessage (MSG) : distribution of the message
- Msg.recycleunchecked () : Message recycling, entering the message pool
Looper.java
...
public static void loop(a) {
finalLooper me = myLooper(); .finalMessageQueue queue = me.mQueue; .for (;;) {
// Iterate through the message pool to get the next available message
Message msg = queue.next(); // might block.try {
// Distribute the messagemsg.target.dispatchMessage(msg); . }catch (Exception exception) {
...
} finally{... }...// Recycle the message into the message poolmsg.recycleUnchecked(); }}...Copy the code
Traverse the message
The key way to traverse a message must be the following
- Message MSG = queue.next() : next() in the Message class; Of course this must be used in conjunction with the outer for (infinite loop) to traverse the message queue
Take a look at the next() method in this Message
- Next () : Simplified some of the source code, complete the left click method name
MessageQueue.java
...
Message next(a) {
final longptr = mPtr; .int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for(;;) {...// Block unless timeout or wake up is reached
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;
// This is about syncbarriers
if(msg ! =null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
if (now < msg.when) {
// Each message processing takes time, with a time interval (when is the point at which it will be executed).
// If the current time is not yet when, calculate the time difference and pass in nativePollOnce to define the time to wake up the block
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// The asynchronous message is removed from the queue and returned. After the return, the message is removed from the queue
//mMessage is still in the position of undistributed synchronous messages
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
// Return a Message that meets the criteria
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// This handles calls to IdleHandler, with several conditions
//1, current message queue is empty (mMessages == null)
// now < mmessages.when)
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0; }}Copy the code
Summarize the meaning expressed in the source code
-
Next () has an infinite loop inside, and you may be wondering why a message that takes down a node should have a loop.
- This loop is necessary to perform delayed messages, synchronization barriers, and so on
-
NativePollOnce blocking method: The blocking state is unblocked after timeout (nextPollTimeoutMillis) or by wake (nativeWake)
- If nextPollTimeoutMillis is greater than or equal to zero, it is specified to sleep and then wake up during this period
- When the message queue is empty, nextPollTimeoutMillis is -1, blocking. Messages are re-queued and the nativeWake method is triggered when the header is inserted
-
If msg.target == null is zero, the synchronization barrier state is entered
- MSG messages are looped to the end node unless an asynchronous method is encountered
- If a synchronization barrier message is encountered, it will theoretically continue in an infinite loop and no message will be returned unless the synchronization barrier message is removed from the message queue
-
When determination of the current time and return message
-
Message when indicates the time when the message is sent. If the message is delayed, it is the time when the message is sent + the delay time
-
If the current time is smaller than when of the returned message, the time difference is calculated and the timeout period is set for nativePollOnce. When the timeout period expires, the blocking is removed and the message is recycled
-
When when the current moment is greater than the returned message: returns to get the available message
-
-
When the message returns, mMessage is assigned to the next node of the returned message (only for synchronous messages that do not involve synchronization barriers)
Here’s a simple flow chart
Distribution of the message
Distribution of the message is the main code: MSG. Target. DispatchMessage (MSG);
So this is the dispatchMessage(MSG) method in the Handler class
- dispatchMessage(msg)
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}Copy the code
As you can see, the code here is already stated in the receive message section of the send and receive column, so there is no need to repeat it here
The message pool
Msg.recycleunchecked () is used to process messages that have been distributed. The distributed messages are not recycled. Instead, they are added to the message pool to be reused
- recycleUnchecked()The code for retrieving messages is quite simple, let’s analyze it
- First, the messages that have been distributed and processed are reset, and flags are available
- The header of the message pool is assigned to the next node of the current reclaimed message, and the current message becomes the header of the message pool
- In short: The reclaim message is inserted into the message pool as a header
- Note that the message pool has a maximum capacity. If the size of the message pool is greater than or equal to the default maximum capacity, the message pool will not accept reclaimed messages
- The default maximum capacity is 50: MAX_POOL_SIZE = 50
Message.java
...
void recycleUnchecked(a) {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this; sPoolSize++; }}}Copy the code
Take a look at the message pool reclamation message schema
Since there is an operation to recycle used messages into the message pool, there must be a way to retrieve messages from the message pool
- obtain(): Very little code, take a look
- If the message pool is not empty: the header of the message pool is fetched directly, and the next node whose header is removed becomes the header of the message pool
- If the Message pool is empty: return the new Message instance directly
Message.java
...
public static Message obtain(a) {
synchronized (sPoolSync) {
if(sPool ! =null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
returnm; }}return new Message();
}
Copy the code
Take a look at the diagram of getting a message from the message pool
IdleHandler
In MessageQueue class next method, you can find about IdleHandler processing, you can not think of it as a special form of Handler, this thing is an interface, which abstracts a method, the structure is very simple
- Next () : simplified a lot of source code, only retain IdleHandler processing related logic; Click on the left method name to complete it
MessageQueue.java
...
Message next(a) {
final longptr = mPtr; .int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for(;;) {...// Block unless timeout or wake up is reached
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; .// This handles calls to IdleHandler, with several conditions
//1, current message queue is empty (mMessages == null)
// now < mmessages.when) // now < mmessages.when
PendingIdleHandlerCount < 0 indicates that the IdleHandler operation is executed only once in the for loop
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if(! keep) {synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0; }}Copy the code
In fact, from the above code, you can analyze a lot of information
IdleHandler Information
-
Call conditions
- The current message queue is empty (mMessages == NULL) or has not reached the time when the return message is distributed
- In an infinite loop that fetches an available message, the IdleHandler is processed only once: the pendingIdleHandlerCount is 0 and the loop cannot be executed again
-
The queueIdle method in IdleHandler is implemented
- Return false, IdleHandler will be removed from the IdleHandler list only once: the default is false
- Returns true, each time a return message is distributed, there is a chance it will be executed: in
Keep alive
state
-
IdleHandler code
MessageQueue.java ... /** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle(a); } public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); }}public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); }}Copy the code
-
How do I use IdleHandler?
- Here is a simple usage, you can have a look, leave an impression
public class MainActivity extends AppCompatActivity { private TextView msgTv; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); msgTv = findViewById(R.id.tv_msg); // Add the IdleHandler implementation class mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("I am IdleHandler")); mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("I'm The Big Handsome bee.")); // Send and receive messages new Thread(new Runnable() { @Override public void run(a) { String info = The first way; mHandler.post(new Runnable() { @Override public void run(a) { msgTv.setText(info); }}); } }).start(); }// Implement the IdleHandler class class InfoIdleHandler implements MessageQueue.IdleHandler { private String msg; InfoIdleHandler(String msg) { this.msg = msg; } @Override public boolean queueIdle(a) { msgTv.setText(msg); return false; }}}Copy the code
conclusion
- In layman’s terms: When all messages are processed or you send a delayed message, conditions are met for executing IdleHandler during both idle times
- This place needs to be noted, if the delay message time is set too short; The IdleHandler may execute after the message is sent. After all, it takes a little time to get to next. If the delay is set long enough, you can see that the IdleHandler is executing between delays.
- As you can see from the source code, IdlerHandler is used to handle related things during idle moments of message distribution
Synchronization barrier
Here comes the most complex module
Before we can understand the concept of synchronization barriers, we need to understand a few things beforehand
Front knowledge
Synchronous and asynchronous messages
What are synchronous messages? What is asynchronous messaging?
- Seriously, asynchronous and synchronous messages are defined, and completion is defined in a way
- isAsynchronous(): Let’s analyze it
- FLAG_ASYNCHRONOUS = 1 << 1: so FLAG_ASYNCHRONOUS is 2
- Synchronous message: isAsynchronous returns false when flags is 0 or 1 and the message is marked as synchronous
- Flags is 0,1: synchronize messages
- Asynchronous: in theory isAsynchronous returns true for bits from right to left, the second digit being 1; However, Message basically uses: 0,1,2 to draw the conclusion
- Flags 2: asynchronous message
public boolean isAsynchronous(a) {
return(flags & FLAG_ASYNCHRONOUS) ! =0;
}
Copy the code
- setAsynchronous(boolean async): This method affects the value of flags
- Since flags is int, no initial value is assigned, so its initial value is 0
- SetAsynchronous will change flags to 2 if it is passed true, or equal
msg.setAsynchronous(true);
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else{ flags &= ~FLAG_ASYNCHRONOUS; }}Copy the code
- How do I generate asynchronous messages? so easy
Message msg = Message.obtain();
// Set the asynchronous message flag
msg.setAsynchronous(true);
Copy the code
- In general, the default message is not set. Flags are 0, so it defaults to a synchronous message. The following column will examine where setAsynchronous is used
Default message type
The setAsynchronous method is rarely used, so what is the default type of message when it is not used?
- This is how messages are generated and then sent
- enqueueMessage: Normal messages (POST, delayed, non-delayed, and so on) are sent through this method
- Because all messages sent go through the enqueueMessage method and are queued, you can see that all messages are processed
- msg.target = this
- This place is assigned to the target of the Message class!
- Note: As long as a message is sent using something like POST or sendMessage, it can never be a synchronization barrier message!
- With regard to synchronous asynchrony, you can see the connection with mAsynchronous
- As long as mAsynchronous is true, our messages are asynchronous
- As long as mAsynchronous is false, our messages will synchronize messages
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
- Where is mAsynchronous set up?
- This is the assignment to mAsynchronous in the constructor
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) = =0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
- Take a look at some common constructors
public Handler(a) {
this(null.false);
}
public Handler(@NonNull Looper looper) {
this(looper, null.false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
Copy the code
- Conclusion under
- That’s clear! If no special Settings are made: the default messages are synchronous messages
- Default messages all assign a value to their target variable: none of the default messages are synchronization barrier messages
Generate synchronization barrier messages
The next method finds that a message whose target is null is called a synchronization barrier message.
- postSyncBarrier(long when)
- –> Barrier of sync
- A synchronization barrier actually represents what it means, shielding all subsequent synchronization method distributions in the message queue
MessageQueue.java
...
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier(a) {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if(when ! =0) {
while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
returntoken; }}Copy the code
- The mMessage variable, which indicates the message that’s going to be processed, the message that’s going to be returned, you can also think of it as the header message of the unprocessed message queue
- About synchronous barrier messages
- Retrieves an available message from the message pool
- There is an interesting loop operation here. The while operation assigns the mMessages header to the p variable and moves the P node to the next node of the current message
- Whether the header (mMessage) is null
- Not empty: because the above loop operation, will make p node’s message, must be just greater than the current moment, p node’s last node message is the current moment past the message, at this time! Our synchronous barrier message MSG is inserted between these two!
- Null: becomes a header
- The synchronization barrier message is inserted directly into the message queue. It does not set the target attribute and does not pass through the enqueueMessage method, so its target attribute is null
Summary:
The rule of the synchronous barrier message insertion into the message queue is basically the same as the normal sending message insertion above. If the message queue has delayed messages that are longer than the current time, synchronization messages precede these delayed messages.
OK, synchronization barrier message insertion, basically can be understood as: normal non-delayed message insertion message queue!
- Process for inserting synchronous barrier messages into message queues
Synchronous barrier flow
- Next () : streamlined a lot of the source code, keeping only the code related to synchronization barriers; Click on the left method name to complete it
MessageQueue.java
...
Message next(a) {
final longptr = mPtr; .int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for(;;) {...// Block unless timeout or wake up is reached
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;
// This is about the logical block of the SyncBarrier
if(msg ! =null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
if (now < msg.when) {
// Each message processing takes time, with a time interval (when is the point at which it will be executed).
// If the current time is not yet when, calculate the time difference and pass in nativePollOnce to define the time to wake up the block
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// The asynchronous message is removed from the queue and returned. After the return, the message is removed from the queue
//mMessage is still in the position of undistributed synchronous messages
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
// Return a Message that meets the criteria
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1; }... }... }}Copy the code
Get rid of a lot of code that we don’t need to pay attention to, and realize that it’s nothing, just a bunch of if eles for, to analyze
- Message MSG = mMessages: This assignment is important to indicate that mMessage retains the position of the Message at the header of the Message queue even if we do nothing to MSG
- MSG. Target == null: Synchronization barrier message encountered
- The first is a while loop, internal logic that keeps moving the MSG node back
- End two conditions of the while
- MSG moves to the end, i.e. to the message queue end, and assigns itself null (next of the end)
- If a message marked as asynchronous is encountered, the message is allowed for subsequent distribution
- Let’s analyze the different effects of the two release conditions
- Message queues do not contain asynchronous messages
- When we are in synchronization barrier logic, move MSG itself to the tail and assign null (next for the tail).
- MSG is null, indicating that subsequent distribution operations cannot be carried out and the circular process will be repeated
- The mMessage header re-assigns its position to MSG, continuing the same process
- It can be seen that the above logic does act as a synchronization barrier, shielding all subsequent synchronization messages from distribution. Distribution of synchronous messages can continue only if the synchronization barrier message is removed from the message queue
- Message queues contain asynchronous messages
- If there are asynchronous messages in the message queue, the synchronization barrier logic permits the asynchronous messages
- The prevMsg heap in the synchronization barrier is assigned! Remember that in the whole method, only the synchronization barrier logic has prevMsg assigned! Whether this parameter is null or not greatly affects the message queue node
- PrevMsg is empty: MSG next is assigned to mMessage directly; After the message is distributed, the header node is removed and the next node of the header node is assigned as the header node
- PrevMsg not empty: does not commit to mMessage; It changes the position of the next node of the previous node that distributes the message to the next node of the distribution node, which is a little bit convoluted
- Through the above analysis, it can be seen that; After an asynchronous message is dispatched, it is removed from the message queue with the same header position
- Message queues do not contain asynchronous messages
The text has written a lot, I am also as detailed as possible, synchronization barrier logic code block will produce the effect of the whole diagram, deepen the impression!
Synchronous barrier action
So what does this synchronization barrier do?
The urgent question is where is the postSyncBarrier(long when) method used, which is not exposed to the outside world and can only be called by the inner package
I searched the entire source package and found that it was used in only a few places, excluding the test class, MessageQueue class, and the viewrotimpl class and the Device class
Device class
-
PauseEvents () : What is involved inside the Device is that when the Device is turned on, a synchronization barrier message is added to block all subsequent synchronization message processing
- PauseEvents () is a method of the private internal DeviceHandler class in the Device class
- That says, we can’t call this method; In fact, we can’t even call the Device class. Device belongs to the hidden class, along with the Event and Hid classes, which the system doesn’t want to expose
- The system really doesn’t want to expose the way messages are inserted into the synchronization barrier; Not including, of course, the unconventional method: reflection
- PauseEvents () is a method of the private internal DeviceHandler class in the Device class
-
Add synchronization barrier: Add synchronization barrier upon startup
Device.java ... private class DeviceHandler extends Handler {...@Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_OPEN_DEVICE: ... pauseEvents(); break; . }}public void pauseEvents(a) { mBarrierToken = getLooper().myQueue().postSyncBarrier(); } public void resumeEvents(a) { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; }}Copy the code
-
Synchronization barrier Removal: Remove the synchronization barrier after the system is powered on
Device.java ... private class DeviceHandler extends Handler {...public void pauseEvents(a) { mBarrierToken = getLooper().myQueue().postSyncBarrier(); } public void resumeEvents(a) { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; }}private class DeviceCallback { public void onDeviceOpen(a) { mHandler.resumeEvents(); }... }Copy the code
-
The overall process of using synchronization barriers in Device is relatively simple, which is briefly described here
- When the device is turned on, a synchronization barrier message is sent that blocks all subsequent synchronization messages
- Remove synchronization barrier messages after the startup is complete
- Conclusion: Obviously, this is to speed up the opening of the device as much as possible without being disturbed by other secondary important events
ViewRootImpl class
For the analysis of this column, one very important conclusion must be referenced in the article that gives this conclusion: source analysis _Android UI when refresh _Choreographer
-
ScheduleTraversals () : Very important
ViewRootImpl.java ... void scheduleTraversals(a) { if(! mTraversalScheduled) { mTraversalScheduled =true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code
-
Conclusion: Source analysis _Android UI when to refresh _Choreographer
- As for the analysis of the above method, the overall process is quite troublesome, involving the analysis of the whole refresh process
- The previous article analyzed the UI refresh process and came to a very important conclusion
When we call the View’s requestLayout or invalidate, we eventually trigger the ViewRootImp to execute the scheduleTraversals() method. In this method ViewRootImp will register a listener to receive Vsync through Choreographer, and after receiving the Vsync sent from the system layer we will perform doTraversal() to redraw the interface. According to the above analysis, when we call refresh operations such as invalidate, the system will not refresh the interface immediately, but will refresh the page after the Vsync message.
We already know that the requestLayout (or invalidate) refresh process must trigger the scheduleTraversals() method, which adds the synchronization barrier message, so there must be a step to remove the synchronization barrier message. This step will most likely be in the doTraversal() method, so take a look at this method
- doTraversal(): removeSyncBarrier! I giao! Here it is!
- This place does two things: remove sync barriers and performTraversals.
void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false; }}}Copy the code
-
How is doTraversal() invoked?
-
Call: mTraversalRunnable used in scheduleTraversals()
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void scheduleTraversals(a) { if(! mTraversalScheduled) { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable,null); . }}final class TraversalRunnable implements Runnable { @Override public void run(a) { doTraversal(); }}Copy the code
-
-
PostCallback is a method of the Choreographer class that involves a large amount of messaging, all of which uses asynchronous message setAsynchronous(true), which is related to interface refresh and therefore takes precedence. See the article above for the full flow
-
The core of postCallback is to get the DisplayEventReceiver to register a Vsync notification, and then after receiving the Vsync we perform doTraversal() to redraw the interface
conclusion
- According to the above description of ViewRootImpl, it is necessary to summarize the influence of synchronization barrier on the interface drawing process
- Detailed version summary (non-human version)
When the View’s requestLayout or invalidate is called, scheduleTraversals() is eventually executed, and a synchronization barrier message is inserted into the main thread message queue (stopping all synchronization message distribution). MTraversalRunnable will be added to the mCallbackQueues and registered to receive the listener for Vsync. When the Vsync notification is received, an asynchronous message will be sent that triggers the traversal of the mCallbackQueues method. This will execute the mTraversalRunnable callback we added, which will execute doTraversal(), which will remove the synchronization barrier message from the main thread message queue, and finally perform the draw operation
- Summary of popular edition
A call to requestLayout or Invalidate inserts a synchronization barrier message into the main thread message queue and registers a listener to receive Vsync. When Vsync notification is received, an asynchronous message is sent and the actual draw event is performed: the synchronization barrier message is removed from the message queue before the draw operation is performed
- Below is a circulation diagram for the silent speech print
conclusion
Message insertion comparison
- One important thing to look at again is the difference between sending a normal message and inserting a synchronous barrier message directly into a message queue, as shown in the figure below
- Fetch message: The fetch message is the mMessage, which is the header of the message queue
- Non-delayed messages are sent before synchronous barrier messages
- Delayed messages are queued after synchronous barrier messages if the time is longer than the time they were sent
Vsync
-
About the Vsync
- Vsync signal is generally generated by the hardware, and the current mobile phone is generally 60Hz ~ 120Hz, refresh 60 to 120 times per second, a time slice counts as a frame
- The time between each Vsync signal is the time period of a frame
-
Take a look at the time slice for synchronizing messages: this picture is really hard to draw, vomiting blood
- In extreme cases, the message you send may have a one-frame delay in its distribution
conclusion
The summary
- Synchronization barriers ensure that asynchronous messages in message queues are preferentially executed
- In view of the difference between normal messages and synchronization barrier messages inserted into the message queue, synchronization barriers are able to timely synchronize messages in the barrier queue
- In extreme cases, a message may be sent with a frame delay before it is distributed
- Extreme scenario: After Vsync signal arrives, RequestLayout and other operations are executed immediately
- The synchronization barrier ensures that the actual drawing of the page takes place immediately after the Vsync signal arrives in the UI refresh
Suggestions for using synchronous and asynchronous messages
Asynchronous messaging is definitely not recommended under normal circumstances. Here is a scenario: Because a need, you send a lot of asynchronous messaging, due to the particularity of the message into the message queue, the system sends an asynchronous message, also can the darling of the row behind your asynchronous messaging, assuming your asynchronous messaging occupy a lot of time, or even take up a few frames, cause the system UI refresh asynchronous message cannot be implemented in a timely manner, is likely to happen at this time frame
Of course, if you can read what is written in the synchronization barrier section, you will have a good idea of when to set messages to asynchronous
- Under normal circumstances, continue to use synchronous messages
- In special cases, messages that need to be sent themselves are preferentially processed: asynchronous messages can be used
The examination site
Above source code basic analysis to this side, let’s see according to these knowledge points, can raise some questions?
A little bit of knowledge
I’ve been browsing some forums and found people confused about how the Handler interacts with the main thread and the child thread.
-
If read this whole article, perhaps your heart has already had the answer, in order to make this knowledge more clear, I still summarize here!
- The main thread and the child thread interact with each other through the handler, and the vehicle for the interaction is the Message object, and in fact all the messages that we send in the child thread are added to the Message queue in the main thread, and then the main thread distributes those messages, so it’s very easy to interact with each other.
-
You may wonder why the message I sent from the child thread was added to the message queue of the main thread.
- If you look at your own code, does your handler object start on the main thread? Does a child thread send a message through this handler?
- The handler simply adds the sent message to the MessageQueue (mLooper variable) of the Looper object it holds
-
So, in which thread do you initialize the Handler object, and in different threads, use this object to send messages; Will distribute messages in the thread where you initialized the Handler object
Is there an upper limit to the number of message queues in the main thread of this Handler? Why is that?
It’s a bit of a sneaky question, and it might make you think, is there an upper limit? As opposed to just saying what’s the upper limit?
The Handler main thread must have a maximum number of message queues. Each thread can instantiate only one Looper instance. Prepare can only be used once
Important: Only one Looper() instance can be instantiated per thread. Message queues reside in Looper
Extension: The MessageQueue class, in fact, is the maintenance of mMessage, only need to maintain this header, can maintain the entire list of messages
2. There is an infinite Loop in Handler. Why is it not stuck? Why didn’t ANR occur?
First, ANR: can’t respond to screen touch events or keyboard input events for 5 seconds; [Fixed] The broadcast onReceive() function is not processed for 10 seconds The front desk service is not completed within 20 seconds, and the background service is not completed within 200 seconds; The ContentProvider publish is not complete within 10 seconds. The Loop Loop is not related to ANR, which is correct. Therefore, after the event is triggered, the time-consuming operation is still handled by the child thread, and the handler communicates the data to the main thread for related processing.
A thread is essentially a runnable piece of code, and when it’s finished, it destroys itself. Of course, we don’t want the main thread to be over, so the whole loop keeps the thread alive.
Why he didn’t get stuck: According to the analysis in event distribution, if there is no message in the next() method that obtains the message, nativePollOnce method will be triggered to enter the thread sleep state and release CPU resources. There is a native method nativeWake method in MessageQueue. It can remove the dormant state of nativePollOnce. Ok, let’s give the answer based on these two methods
-
When the MessageQueue is empty, the nativePollOnce method in MessageQueue is triggered to sleep the thread and release CPU resources
-
Message insertion into the message queue triggers the nativeWake method to release the main thread from sleep
- The wake up method is triggered when a message is inserted into a message queue as a message queue header
- The wake up method is not triggered when a message is inserted into a message queue at a position in the chain after the header
-
To sum up: If the message queue is empty, it blocks the main thread and releases resources. The message queue is empty, and when a message is inserted, the wake up mechanism is triggered
- This logic ensures that the main thread uses maximum CPU resources and can sleep itself in a timely manner without wasting resources
-
In essence, the main thread is essentially message-driven
3. Why not update the UI in child threads?
Multithreaded operation, in the UI drawing method represents unsafe, unstable.
Consider A scenario: I need to change A circle, thread A doubles the circle size, and thread B changes the color of the circle. When thread A increases the volume of the circle by one third, thread B reads the data of the circle and changes the color. The end result may be the wrong size and color…
4. Can the message sent by myself be executed first? How does it work?
This question, I feel, can only be said: with a synchronization barrier, yes.
Synchronization barrier function: In a message queue that contains a synchronization barrier, all synchronous messages in the message queue are masked and asynchronous messages are allowed to be distributed.
In the case of a synchronization barrier, I can set my messages to be asynchronous and have the effect of being executed first.
5. What are the disadvantages of using Handler to communicate with child threads?
The child thread communicates with the child thread using Handler. A child thread that receives a message must instantiate Handler, and Looper must be performed. Looper.
If the communication operation is finished, we can usually end the distribution operation with: mhandler.getlooper ().quit()
- The quit() method performs several operations
- Emptying message queues (undistributed messages, no longer distributed)
- The native destruction method is called
nativeDestroy
(Guess: maybe some resources are released and destroyed) - Deny new messages to the message queue
- It can end the loop() loop of sending messages
- Develop: quitSafely() ensures that all unfinished business is completed before ending the message distribution
6. The blocking wake mechanism in Handler?
This blocking wake up mechanism is implemented by ePoll, a Linux-based I/O multiplexing mechanism that monitors multiple file descriptors at the same time and notifies the corresponding program to read/write when a file descriptor is ready.
When MessageQueue is created, nativeInit is called, a new epoll descriptor is created, and then some initialization is done and the corresponding file descriptor is listened. After the epoll_wait method is called, it is blocked. NativeWake triggers the write method on the operator and listens for the operator to be called back, ending the blocking state
See more: Synchronization barrier? Blocking wake up? Reread the Handler source with me
What is IdleHandler? What conditions trigger IdleHandler?
IdleHandler is essentially an interface, designed to handle things when message distribution is idle
Specific conditions: when the message queue is empty, when the delay message is sent
8. After the message is processed, is it destroyed directly? Or is it recycled? If it is recycled, is there a maximum capacity?
The Handler is used to reset the data of the message pool after processing the message. The header is inserted into the message pool to save time
The maximum capacity of the message pool is 50. When the maximum capacity is reached, no messages can be received
Incorrect use of handlers causes memory leaks. How to solve it?
First, the Looper object is in the main thread, and the entire lifetime of the MessageQueue is in the Looper object, that is, the MessageQueue is in the main thread; We know that Message needs to hold Handler instances, and Handler has a strong reference to the Activity
When we close the current Activity, the Message sent by the current Activity is not processed in the Message queue, and Looper indirectly holds the reference to the current Activity, because the two are directly strong references and cannot be disconnected, so the current Activity cannot be reclaimed
Disconnect the reference between the two parties. After the message is processed, the reference between the two parties will be reset and disconnected
Solution: Use a static inner class to weaken the Activity and empty the message queue
The last
Writing this article and mind mapping, I also made about 13 pictures. I really tried my best!