- Android Handler mechanism (1
- Android Handler mechanism (2) source code parsing
- The Android Handler mechanism (3) is leaking memory
Introduction of Handler
The initial perception of handler is to switch threads to update the UI, but if you look at the source code of handler, you will find that the handler mechanism plays a crucial role in the operation of the entire APP, not just to switch threads
Basic usage
I explained this in detail in my first article on the Handler mechanism, which you can find on my home page
Source code analysis
Let’s take a look at the core classes involved in the Handler mechanism:
The name of the class | describe |
---|---|
Message | The entity that sends the message. The class uses several fields, such as what and obj, to store information |
MessageQueue | Is a queue data structure for linked list implementations that manage messages, first in, first out |
Looper | The MessageQueue is monitored through an infinite loop, which pulls the Message out for processing each time a Message is sent to the queue |
Handler | Process the message |
ThreadLocal | Thread-isolated classes that store data within a thread |
Start with a general understanding of these classes
Message enqueue mechanism
Let’s start with the familiar handler. sengMessage(Message MSG) method:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
Copy the code
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code
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);
}
Copy the code
The call chain of the above three methods is
sendMessage -> sendMessageAtTime -> sendMessageAtTime
Copy the code
The function of these three methods is to send messages, and the parameter delayMillis indicates when the message is removed from the message queue in milliseconds. You can see that sendMessage calls the sendMessageDelayed method internally
return sendMessageDelayed(msg, 0);
Copy the code
Messages sent through the handleMessage method are processed immediately, with a delay of 0. The last thing I call is
enqueueMessage(queue, msg, uptimeMillis);
Copy the code
The purpose of this method is to enqueue messages, which brings us to the first core class of the Handler mechanism: MessageQueue, which enqueues sent messages to MessageQueue.
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // At comment 1, highlight
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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();
return false;
}
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.
// At comment 2
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
Notice the first comment
msg.target = this; // at comment 1
Copy the code
This, of course, is the handler instance of our new, that is, the reference to the handler that Message holds. Highlight it. We’ll test it later. Then look at the enqueueMessage method. The main logic of this method is queue entry. If you look closely at the insert logic, you will see that instead of inserting the new MSG into the end of the queue, you insert the new MSG into the head of the queue
// New head, wake up the event queue if blocked.
// At comment 2
msg.next = p;
mMessages = msg;
needWake = mBlocked;
Copy the code
Notice the mMessages member variable, which is the header of a linked list. If you know the data structure, the header of a linked list is a linked list, because you can find all the elements in the list through the header. At this point, the queuing part of the Handler mechanism is done. Have you found a very important problem that was ignored? Just now, it was said that the sent message was queued, so where did this messagequeue get from? Looking at the code we wrote, there is only one possibility, and that is when new Handle() :
public Handler(@Nullable Callback callback, boolean async) {...// at comment 1
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// At comment 2
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
This brings us to the second core class in the Handler mechanism: Looper. The queue here is actually obtained by Looper, and then look at looper.mylooper ();
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
The third core class, ThreadLoal, is used to store thread-wide data, that is, to get the Looper of the current thread from ThreadLocal. Since the Handler is created on the main thread new, it gets the Looper of the main thread. MessageQueue is then obtained through Looper. A thread has only one Looper and a MessageQueue in the Looper, so a thread has only one Looper and one MessageQueue:That’s pretty much the end of the queue section, but one more question: when did Looper set to the main thread? Let’s move on
Analysis of the
The whole process is analyzed up to this point, just sending the message, queuing the message to MessageQueue. I don’t see where to call the handleMessage method that uses handler to be overridden. Where exactly is it called? The Looper class keeps asking MessageQueue if there are any new messages inserted, and if there are any new messages, it takes them out and processes them. Where does that start the endless loop? It’s easy to think of the entry to the application, the main function of ActivityThread (if you’ve seen the Activity startup process, it’s actually called from the Zygote process using reflection).
The APP keeps running and never exits because Looper keeps looping.
Circulation mechanism
public static void main(String[] args) {...// at comment 1
Looper.prepareMainLooper();
// 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 = args.length - 1; i >= 0; --i) {
if(args[i] ! =null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
// At comment 2
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// At comment 3
Looper.loop();
// Comment 4
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
Sure enough, I see a familiar figure. Look at note 1
Looper.prepareMainLooper();
Copy the code
public static void prepareMainLooper(a) {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code
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));
}
Copy the code
This is where the Looper object is created for the main thread during APP startup and then set to ThreadLocal. Then look at the Looper constructor
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
The messagequeue object is new, which was assigned to Message by Looper when the Looper was initialized. Here you can see the relationship between Looper and Messagequeue: Looper contains Messagequeue, which is a member variable of the Looper object, and there is only one Messagequeue per thread.
The relationship of four classes
So far, the relationship of four classes can be found:
Looper ---->(reference) MessageQueue ---->(reference) Message -->(reference)Handler // The latter is a member of the former // Once you know the reference relationship, you can try to analyze, Why do I have memory leaks with handlers? // Memory leaks will answer this question in article 3Copy the code
Now that we’re done with the prepareMainLooper() method, what does it do altogether?
- Create a Looper object and store it in ThreadLocal, thread-specific
- Create a MessageQueue object. MessageQueue is a member variable of Looper, so MessageQueue is also thread-specific
Now that the main thread’s Looper and messageQueue objects are created, where does the loop start, of course, the loop method at comment 3 below
// At comment 3
Looper.loop();
Copy the code
public static void loop(a) {
// This method, described earlier, gets the current thread's Looper object
final Looper me = myLooper();
// If the handler is created in the child thread without manually opening the loop
// This exception will be raised for obvious reasons, you are not calling it in the current thread
// the looper.prepare() method creates the looper object and messageQueue object for the current thread
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// Get the MessageQueue object for the current thread
finalMessageQueue queue = me.mQueue; .// Start an infinite loop here
for (;;) {
// Retrieve the next Message from the Message queue
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; }...try {
// at comment 1
// MSG is processed with the dispatchMessage method of the target field
msg.target.dispatchMessage(msg);
if(observer ! =null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
// recycle the MSG
// Inside this method, each field in MSG is set to null or default
// Then add the MSG to the cache pool
// The size of the cache pool is 1msg.recycleUnchecked(); }}Copy the code
At this point, we’ve finally found a place to start the infinite loop, and there are several important things about this method
- Queue.next () to get the next message of the messageQueue.
- msg.target.dispatchMessage(msg); Pass the message to handler for processing.
The rest of the some important places have comments I wrote the Message the next () method is also very important, although the effect is very simple, is to get the next Message Message in the list, but the code implementation may not be as simple as that, here I will not make analysis, we are interested in can see, I put the post code, need to note is that, Instead of letting the for loop run endlessly, consuming CPU resources, the next() method blocks when there are no messages in the queue.
@UnsupportedAppUsage
Message next(a) {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if(nextPollTimeoutMillis ! =0) {
Binder.flushPendingCommands();
}
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 && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
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);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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
Message handling mechanism
Finally, message processing is analyzed. In this method, you can see handleMessage(MSG); This is the method that we copied when we used Handler. Now let’s look at the dispatchMessage method
// at comment 1
// MSG is processed with the dispatchMessage method of the target field
msg.target.dispatchMessage(msg);
Copy the code
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
/ / the first
handleCallback(msg);
} else {
if(mCallback ! =null) {
/ / the second
if (mCallback.handleMessage(msg)) {
return; }}/ / the third kindhandleMessage(msg); }}Copy the code
This is one of the methods I focused on in the first article in the Handler series, the logic of processing messages.
finishing
Finally, look at comment 4 for the main function
// Comment 4
throw new RuntimeException("Main thread loop unexpectedly exited");
Copy the code
There is an exception thrown, which means the program should not run here. Why is that? If the loop method is already open, the program will not run to this point. If it runs to this point, it will indicate that there is a problem, and it will report an exception naturally.
thinking
After analyzing the Handler mechanism, I have a big question in my mind. Since it is relying on the loop method in the main thread to keep the app running indefinitely, why can Android still respond to click events? Shouldn’t all logic be jammed up in the loop? The answer is actually very simple. Each system event is packaged into a Message and sent to MessageQueue. Then the Message is received in the loop and corresponding operations are performed. Specific in-depth also involves the communication between processes, which will not be discussed here
Now that we’re done, give us a “like” before we go