preface
This article is mainly to analyze the Handler message mechanism of the key source code, before reading the need to have some basic understanding of the Handler. Here’s a quick recap:
The basic composition of
A complete message processing mechanism consists of four elements:
- Message: The carrier of information
- MessageQueue: A queue used to store messages
- Looper(message loop) : Is responsible for checking if there are messages in the message queue and fetching messages
- Handler(sends and processes messages) : Adds messages to message queues and is responsible for distributing and processing messages
Basic usage
A simple use of Handler is as follows:
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg); }}; Message message =new Message();
handler.sendMessage(message);
Copy the code
Note that the looper.prepare () and looper.loop () methods are called in the non-main thread
The working process
Its working process is shown in the figure below:
The process from sending messages to receiving messages is summarized as follows:
- Send a message
- The message enters the message queue
- Retrieves a message from a message queue
- Processing of messages
Here is a fold of four steps to analyze the relevant source code:
Send a message
Handle has two classes of methods for sending messages, which are essentially indistinguishable:
- sendXxxx()
- boolean sendMessage(Message msg)
- boolean sendEmptyMessage(int what)
- boolean sendEmptyMessageDelayed(int what, long delayMillis)
- boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
- boolean sendMessageDelayed(Message msg, long delayMillis)
- boolean sendMessageAtTime(Message msg, long uptimeMillis)
- boolean sendMessageAtFrontOfQueue(Message msg)
- postXxxx()
- boolean post(Runnable r)
- boolean postAtFrontOfQueue(Runnable r)
- boolean postAtTime(Runnable r, long uptimeMillis)
- boolean postAtTime(Runnable r, Object token, long uptimeMillis)
- boolean postDelayed(Runnable r, long delayMillis)
- boolean postDelayed(Runnable r, Object token, long delayMillis)
There is no analysis of the specific method of characteristics, they are ultimately by calling sendMessageAtTime () or sendMessageAtFrontOfQueue implementation news team operation, the only difference is the post series method calls the getPostMessage before messages:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
Note that sendMessageAtTime() is typically used when called by another sendXxx:
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
Copy the code
If the caller does not specify a delay time, the message is executed at the current time, immediately. All methods exposed by Handler follow this action, and unless specified, MSG message execution time is: current time plus delay time, essentially a timestamp. Of course, you can specify any time you want, which will be used later in the message insert. The code is simple: it simply assigns the Runnable callback passed by the caller to Message. SendMessageAtTime () and sendMessageAtFrontOfQueue methods through enqueueMessage method realizes the message into the stack:
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
The code is very simple, mainly have the following operations:
- Let message hold a reference to the Handler that sent it (this is key to finding the corresponding Handler when processing messages)
- Set whether the message is asynchronous (asynchronous message does not need to queue, through the synchronization barrier, queue execution)
- call
MessageQueue
theenqueueMessage
Method to queue a message
The message enters the message queue
Preparation before joining the team
The enqueueMessage method is the key to adding messages to MessageQueue.
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
/ /... Omit the following code
}
Copy the code
The code is simple: Determine if message’s target is null, and throw an exception if it is. The target is the Handler reference mentioned above in handler. enqueueMessage. The next step is to determine and process the message
boolean enqueueMessage(Message msg, long when) {
/ /... Omit the above code
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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;
/ /... Omit the following code
}
/ /... Omit the following code
}
Copy the code
A synchronized lock is added, all subsequent operations are performed in synchronized blocks, and two if statements are used to handle two exceptions:
- Determine whether the current MSG has been used, if so, exclude exceptions;
- Determine whether the MessageQueue (MessageQueue) is closing, if so, reclaim the message, return join failed (false) to the caller, and print the relevant log
If all is well, mark that the message is in use by markInUse (corresponding to the exception of the first IF), and then set the time when the message is sent (machine system time). Next, perform the insert operations
Queues messages
Continue with the code implementation of enqueueMessage
boolean enqueueMessage(Message msg, long when) {
/ /... Omit the above code
synchronized (this) {
/ /... Omit the above code
/ / step 1
Message p = mMessages;
boolean needWake;
/ / step 2
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
/ / step 3
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if(needWake) { nativeWake(mPtr); }}/ / step 4
return true;
}
Copy the code
MessageQueue uses a one-way linked list to maintain the MessageQueue, following a first-in, first-out soft solution. Analyze the code above:
-
Step 1: mMessages is the header of the list.
-
Step 2: a judgment statement, if the three conditions are met, directly use MSG as the table header:
- If the header is empty, there is no message in the queue. MSG is directly used as the header of the linked list.
when == 0
Indicating that the message is to be executed immediately (e.gsendMessageAtFrontOfQueue
Method, but generally sent messages are sent at time plus delay unless specified), MSG is inserted as the linked list header;when < p.when
, indicating that the message to be inserted is executed earlier than the table header, and MSG is inserted as the linked list header.
-
Step 3: The message is inserted into the appropriate location by repeatedly comparing the execution time of the message in the queue with the execution time of the inserted message, following the principle of small timestamp first.
-
Step 4: Return to the caller that message insertion is complete.
Note needWake and nativeWake in your code, which are used to wake up the current thread. Because at the message fetching side, the current thread will enter the blocking state according to the state of the message queue, and will decide whether to wake up according to the situation at the time of insertion.
The next step is to retrieve the message from the message queue
Retrieves a message from a message queue
Again, look at the preparatory work first
The preparatory work
To use a Handler in a non-main thread, you must do two things
Looper.prepare()
: Create a LoopLooper.loop()
: Start the loop
Let’s ignore its creation and look at the code at the beginning of the loop in sections: first, some checking and judging work, the details of which are commented in the code
public static void loop(a) {
// Get the loop object
final Looper me = myLooper();
if (me == null) {
// If loop is empty, an exception is thrown to terminate the operation
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
// start the loop repeatedly
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
// indicates that loop is enabled
me.mInLoop = true;
// Get the message queue
final MessageQueue queue = me.mQueue;
// Make sure that permission checks are based on local processes,
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow".0);
boolean slowDeliveryDetected = false;
/ /... Omit the following code
}
Copy the code
Operations in loop
The loop is now officially open, with key code reduced:
public static void loop(a) {
/ /... Omit the above code
for (;;) {
/ / step one
Message msg = queue.next();
if (msg == null) {
/ / in step 2
return;
}
/ /... Omit non-core code
try {
/ / step 3
msg.target.dispatchMessage(msg);
/ /...
} catch (Exception exception) {
/ /... Omit non-core code
} finally {
/ /... Omit non-core code
}
/ / step 4msg.recycleUnchecked(); }}Copy the code
Step by step analysis of the above code:
- Step 1: From the message queue
MessageQueue
Queue.next () may block, as discussed below. - Step 2: If the message is NULL, end the loop (null is not returned when there are no messages in the message queue, but is returned when the queue is closed, as described below)
- Step 3: Distribute the message after receiving it
- Step 4: Recycle the messages that have been distributed, and then start a new cycle of fetching data
MessageQueue’s next method
We will look only at the first step of the message extraction, the rest will be seen in a later section, queue.next() has more code, so we will continue to look at the fragment
Message next(a) {
/ / step one
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
/ / in step 2
int pendingIdleHandlerCount = -1;
/ / step 3
int nextPollTimeoutMillis = 0;
/ /... Omit the following code
}
Copy the code
- Step 1: If the message Loop has exited and disposed, return NULL, corresponding to the Loop passed above
queue.next()
Retrieves the message to NULL and exits the loop - Step 2: Initialization
IdleHandler
counter - The third part: the judgment conditions required for the initialization of native. The initial value is 0. If the value is greater than 0, it means that there are still messages to be processed (delayed messages have not reached the execution time), and -1 means that there is no message.
Continue analyzing the code:
Message next(a) {
/ /... Omit the above code
for(;;) {if(nextPollTimeoutMillis ! =0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
/ /... Omit the following code}}Copy the code
This paragraph is easy:
- Start an infinite loop
nextPollTimeoutMillis ! = 0
Call Native if there are no messages in the message queue or all messages are not executedBinder.flushPendingCommands()
Method to send a message to the kernel thread before entering the block so that the kernel can properly schedule and allocate resources- Call the native method again, according to
nextPollTimeoutMillis
When it is -1, the current thread will be blocked (it will re-enter the runnable state when the new message is queued); when it is greater than 0, it indicates that there are delayed messages.nextPollTimeoutMillis
Will act as a blocking time, that is, the message will be executed after a long time.
Moving on to the code:
Message next(a) {
/ /... Omit the above code
for(;;) {/ /... Omit the above code
// Enable synchronization lock
synchronized (this) {
final long now = SystemClock.uptimeMillis();
/ / step one
Message prevMsg = null;
Message msg = mMessages;
/ / in step 2
if(msg ! =null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
/ / step 3
if(msg ! =null) {
/ / step 4
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
/ / step 5
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 {
/ / step 6
nextPollTimeoutMillis = -1; }}/ /... The IdleHandler code is omitted below}}Copy the code
Analyze the code:
- Step 1: Get the queue header
- Step 2: Determine whether the current message is a synchronous message (the target of an asynchronous message is null) and start the loop until the synchronous message is found
- Step 3: Check whether the message is null, perform step 4 if it is not empty, and perform step 6 if it is empty.
- Step 4: Determine the message execution time, if greater than the current time, to the previous mentioned
nextPollTimeoutMillis
Assign a new value (the time difference between the current time and the message execution time), in this step basically completes all the fetch operations of the loop. If the current message does not reach the execution time, the loop ends and a new loop starts, the above mentioned will be usednativePollOnce(ptr, nextPollTimeoutMillis);
The method enters the blocked state - Step 5: Retrieve the message that needs immediate execution from the message queue, end the loop and return.
- Part 6: No message in message queue, flag
nextPollTimeoutMillis
So that the next loop enters the blocking state
The rest of the code is basically handling and executing the IdleHandler, which will be explained in the IdleHandler section.
Processing of messages
Remember on the loop method of MSG. Target. DispatchMessage (MSG); ? Messages are distributed via the dispatchMessage method. Where target is a reference to the handler that MSG holds that sent it, which is assigned when the message is sent. DispatchMessage = dispatchMessage
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
The code is simple: call the callback or handleMessage method by checking whether the Message has a Runable, and hand it to the Handler you define. Note that although callback is a Runable, it does not call the run method, but executes it directly. This means that it does not start a new thread, but is used as a method (which would have been a higher-order function if Handler had been written using Kotlin in the first place).
Other Key points
The main flow of message processing is covered above. Next, the key source code outside the main flow is discussed
The creation of a Loop
Remember that looper.prepare () and looper.loop () are called on non-main threads? These two methods can be understood as initializing the Loop and starting the Loop, and we don’t need to do this in the main thread because the framework layer already does it for us in the main method of app startup. Let’s look at these two methods separately:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { 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(new Looper(quitAllowed)); } private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code
We first use a static ThreadLocal to ensure Loop uniqueness and thread isolation so that a thread has only one Loop instance. Then initialize the Loop and create MessageQueue (quitAllowed setting whether exit is allowed). The Loop is associated with the message queue in this step.
Note that the Loop construction is private and can only be created using the Prepare field and retrieved using the myLooper method.
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Copy the code
ThreadLocal. Get the source code:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }Copy the code
As you can see, each Thread holds a ThreadLocalMap, which uses the same data structure as a HashMap, using ThreadLocal as the key, and value as the Loop instance. Not hard to see: we can only get Loop instances of the current thread.
The Loop method also provides prepareMainLooper, an initialization method in the main thread, but this method explicitly states that it is not allowed to be called, only by the system itself. This is basically the key to creating the Loop, where the Loop is associated with message queues and lines.
The Handler to create
The constructor for Handler has the following functions:
- public Handler()
- public Handler(Callback callback)
- public Handler(Looper looper)
- public Handler(Looper looper, Callback callback)
- public Handler(boolean async)
- public Handler(Callback callback, boolean async)
- public Handler(Looper looper, Callback callback, boolean async)
The first and second of them have been abandoned, Public Handler(Callback Callback, Boolean async) or public Handler(Looper Looper, Callback Callback, Boolean async), their source code is as follows:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } 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) {looper.prepare (); 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
The main difference between the two methods is that one uses the passed loop, the other uses the loop directly from the current thread, and then the same initialization operations. The key point here is that the handler handles the message on a thread that has nothing to do with the thread that created it, but rather with the thread in the loop at which it was created.
This is why handlers can switch threads: Handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler Or sendMessage, the final Handle Message is executed in the main thread.
Message creation, recycling, and reuse mechanisms
We can create a Message directly using the new keyword:
Message message = new Message();
This is not recommended, however, and there are several methods for creating messages:
These methods essentially create message by obtaining (), except for the parameters that are used to assign values to different member variables of message:
public static final Object sPoolSync = new Object(); Message next; private static Message sPool; public static Message obtain() { synchronized (sPoolSync) { if (sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }Copy the code
This side of the code is simple. Message maintains a single linked list inside, using sPool as the header to store Message entities. You can see that each time the caller needs a new message, it fetches it from the head of the list and returns it directly. A new message is created when there is no message.
So when does the linked list insert the message? Next, the Message collection:
public static final Object sPoolSync = new Object(); private static final int MAX_POOL_SIZE = 50; void recycleUnchecked() { 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
This method is called every time a message is removed from the MessageQueue queue for distribution, as in the loop.loop () method mentioned above. The code is simple: restore the member variables of Message to their original state, and then insert the reclaimed Message into the linked list using header interpolation (limited to a maximum size of 50). In addition, the same lock is used to insert and take out the operation, which ensures security.
Note that inserts and retrieves are done at the head of the list, not the same as in the message queue. Although the use of one-way linked list, recycling using head plug and head out, in after out, is a stack. In MessageQueue, however, it is a queue, following the principle of first in, first out, and the position of insertion is determined according to the state of the message, and there is no fixed insertion node.
This is a typical share mode, the biggest characteristic is to reuse objects, avoid the memory waste caused by repeated creation. This is why Android officially recommends creating messages this way: to improve efficiency and reduce performance overhead.
IdleHandler
IdleHandler is simply defined as an interface defined in MessageQueue:
public static interface IdleHandler {
boolean queueIdle();
}
Copy the code
In the Looper loop, the queueIdle method is executed whenever the message queue is idle: there is no message or no message execution time needs to be delayed. The returned Boolean indicates whether the IdleHandler is permanent or disposable: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler: IdleHandler
- Ture: permanent, executed as soon as idle
- False: One-off. The value is executed only when it is idle for the first time
It can be used as follows:
Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return true;
}
});
Copy the code
Take a look at the implementation of addIdleHandler
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); }}Copy the code
The code is simple, just a List to hold the implementation of the interface. So how does it implement the call when it’s idle?
Remember the code that was omitted from MessageQueue’s next method above?
Message next() { //... Int pendingIdleHandlerCount = -1; // -1 only exists in first iteration for (;;) {/ /... Omit no relevant code / / second step 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; Posthandlers == null {posthandlers = new posthandlers [math. Max (pendingIdleHandlerCount, 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; // Step 6 Try {keep = idler.queueidle (); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // Step 7 if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); PendingIdleHandlerCount = 0; }}Copy the code
Let’s analyze the code step by step:
- Step 1: Create local variables before the message fetch loop begins
pendingIdleHandlerCount
Used to recordIdleHandler
The number of, is -1 only at the beginning of the cycle; - Step 2: When not retrieved
Message
The message (no message or no message available for immediate execution, and no blocked state) or the message needs to be delayedpendingIdleHandlerCount
Assignment recordIdleHandler
The number of; - Step 3: Judge
IdleHandler
Quantity, if notIdleHandler
, directly ends the current loop and marks the loop to enter the pending state. - Step 4: Determine if it is the first time and initialize
IdleHandler
The List of - Step 5: Start walking through all of them
IdleHandler
- Step 6: Do it one by one
IdleHandler
thequeueIdle
methods - Part seven: According to each
IdleHandler
thequeueIdle
The return value ofIdleHandler
Permanent or one-time, remove non-permanent items from the array; - Step 8: Modify
IdleHandler
Quantity information ofpendingIdleHandlerCount
To avoidIdleHandler
Repeat the execution.
This is the core principle of IdleHandler, which is fired only when the message queue is empty, or when the message queue header is delayed. When the message queue header is a delayed message, it fires only once. As we saw in the fetch message section, delayed messages that end the current loop and enter the next loop trigger blocking.
Handler is used in the Framework layer
Have you ever wondered why Android calls looper.prepare () and looper.loop () on the main thread for you? Isn’t that a bit of an overkill?
In fact, it’s not that simple. If you look at the source code for the Framework, you’ll see that the entire Android app runs on Handler. The operation of the four major components, their life cycle is also based on the Handler event model, and click events. All these are generated by the Android system framework layer corresponding message and handed to a Handler for processing. This Handler is the ActivityThread inner class H. Post a screenshot of its code
As you can see, handlers are involved in the life cycle of all four components, even when they are out of memory.
This also explains why performing time-consuming tasks on the main thread can result in UI stuttering or ANR: Because all the main thread, that is, the logic code of the UI thread, is executed in the life cycle of the component, and the life cycle is controlled by the event system of the Handler. When a time-consuming operation is executed in any life cycle, the subsequent messages in the MessageQueue MessageQueue cannot be processed in time, resulting in delay. Until the visual blockage, severe cases will further trigger the occurrence of ANR.
If you’re interested, take a look at the code.
Related series of articles recommended:
- Blog.csdn.net/qingtiantia…
- Blog.csdn.net/qingtiantia…
- Blog.csdn.net/qingtiantia…
- Blog.csdn.net/qingtiantia…