Handler questions are a must in an Android interview, but do you know everything about Handler?
First, topic level
- Basic principles of Handler
- How do I use Handler in child threads
- How does MessageQueue wait to get messages
- Why not use epoll instead of wait?
- Thread and Handler Looper MessageQueue
- How to ensure thread safety when multiple threads send messages to MessageQueue
- How are Handler message delays handled
- View.post and handler. post
- Memory leakage caused by Handler. Procedure
- Is it true that non-UI threads can’t manipulate views
Second, detailed explanation of the topic
The code analysis is based on Android SDK 28
You can first look at the above question to think about it, if it is clear, the following article is not necessary to see ~
1. Basic principles of Handler
There is no need to say more about how this Handler works. You should know that it can be illustrated with a picture (image from the network).
2. How to use Handler in child thread
In addition to the Handler basics above, how to use handlers in child threads is also a common problem. To use Handler in a child thread, you need to perform two operations: looper. prepare and looper. loop. Why do we need to do this? What do Looper.prepare and Looper.loop do? We know that if we create a Handler directly from a child thread, we will get the following error:
"Can't create handler inside thread xxx that has not called Looper.prepare()
Copy the code
We can look at the Handler constructor, which evaluates the Looper. If the Looper obtained from ThreadLocal is empty, the error is reported.
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()"); }}public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
So what does Looper. Prepare do?
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
Prepare creates a Looper and sets it to ThreadLocal. The detail here is that each Thread must have only one Looper, otherwise it will throw an exception. Looper.loop reads the message from MessageQueue and executes it.
The general question that comes up here is why don’t you call these two methods manually in the main thread? As I’m sure you all know, activityThread. main is already called. From this point of view, ActivityThread can be referred to, but I won’t go into details here.
3. How does MessageQueue wait for messages
Looper. Loop starts reading messages in the MessageQueue. What does Looper do when there is no message in the MessageQueue? We know we’re waiting for news, but how?
Looper. Loop: Messagequeue.next () gets the message. If there is no message, it will be blocked.
public static void loop(a) {
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; }}}Copy the code
Message next(a) {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
// ...}}Copy the code
The native method nativePollOnce is called in MessageQueue. Next.
// android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
// ...
mLooper->pollOnce(timeoutMillis);
// ...
}
// Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
// ...
result = pollInner(timeoutMillis);
// ...
}
int Looper::pollInner(int timeoutMillis) {
// ...
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}
Copy the code
As you can see from the above code, epoll_wait is finally used to wait on the Native side. Epoll_wait here is one part of the epoll mechanism in Linux, not too much about epoll mechanism here is introduced, we are interested in can reference segmentfault.com/a/119000000…
Why not use Java wait/notify instead of native epoll?
4. Why use epoll instead of wait?
Wait/notify in Java can also block waiting for messages, as it did in Android 2.2 and before. You can refer to the commit www.androidos.net.cn/android/2.1… So why change to using epoll? Java wait/Notify is not enough to handle native events by looking at the commit record. Specific changes is the commit android.googlesource.com/platform/fr…
Sketch of Native input for MessageQueue / Looper / ViewRoot
MessageQueue now uses a socket for internal signalling, and is prepared
to also handle any number of event input pipes, once the plumbing is
set up with ViewRoot / Looper to tell it about them as appropriate.
Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3
Copy the code
But we started with select, and then we changed it to epoll. Indicates that this commit specific android.googlesource.com/platform/fr…
As for the difference between select and epoll, it is not detailed here, you can see it in the reference article above.
5. Relationship between threads and Handler Looper MessageQueue
The relationship here is one thread for one Looper for one MessageQueue for multiple handlers.
6. How to ensure thread safety when multiple threads send messages to MessageQueue
Since one thread corresponds to one MessageQueue, how can multiple threads ensure thread safety when sending messages to MessageQueue? It’s a simple matter of adding a lock.
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// ...}}Copy the code
7. How is the Handler message delay handled
Another question that handlers have raised is how are delayed messages handled in handlers? Timers or something else? Here we start from the event initiation:
// Handler.java
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
// The time passed is uptimeMillis + delayMillis
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// ...
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
/ / call MessageQueue. EnqueueMessage
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
From the point of the code above logic, Handler after post messages, call until MessageQueue. EnqueueMessage, one of the most important step is to the incoming time is uptimeMillis + delayMillis.
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// ...
msg.when = when;
Message p = mMessages; // Next message
// Insert messages into the sequence sorted by when
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Find the appropriate node
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break; }}// Insert operations
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// Wake up the queue to fetch the message
if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code
As we can see from the above code, when a delayed message is posted, the MessageQueue is sorted by the length of when. And then let’s see how we use “when”.
Message next(a) {
// ...
for (;;) {
// Use epoll_wait to wait for the nextPollTimeoutMillis message
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// The current time
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if(msg ! =null && msg.target == null) {
// Get a valid message
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
if (now < msg.when) { // Delay execution is required, pass; NativePollOnce timeout to delay
// Get the time required to wait for execution
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { // Immediately executed messages are returned directly
// Got a message.
mBlocked = false;
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// Execute the contents of IdleHandler if there is no message to execute
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// If there is no IdleHandler to execute, wait for the message to execute
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Execute idle Handlers
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;
NextPollTimeoutMillis = 0; // If idle Handlers are executed, the message might be ready for execution, so do not wait to check whether the message can be executed
nextPollTimeoutMillis = 0; }}Copy the code
From the above code analysis, we know that when Handler. PostDelayd is executed, the following steps are performed:
- Convert the delay time we passed in to the number of milliseconds to boot time
- In MessageQueue, the order is sorted according to the time of the previous transformation
- When messagequeue.next gets the message, compare the current time (now) with the time of the first transformation (when). If now < when, wait through the timeout of epoll_wait
- If the message needs to wait, the Idel Handlers are executed, and then the handlers are checked to see if the message can be executed
View. Post and Handler.post
Handler.post is one of the most commonly used Handler functions. We also use view. post. Let’s take a look at the code for view.post.
// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
Copy the code
From the code, if AttachInfo is not empty, it is executed through handler, if handler is empty, it is executed through RunQueue. So let’s look at what AttachInfo is here. This needs to be traced back to the view wrotimPL process, so let’s look at the following code.
// ViewRootImpl.java
final ViewRootHandler mHandler = new ViewRootHandler();
public ViewRootImpl(Context context, Display display) {
// ...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
}
private void performTraversals(a) {
final View host = mView;
// ...
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
mFirst = false;
}
// ...
}
Copy the code
In the ViewRootImpl constructor, we create mAttachInfo, and then in performTraversals, if mFirst is true, Call the host dispatchAttachedToWindow, the host is DecorView here, if you have the reader friends here not too clear, can see the front the interviewer take you learn android – from the View of drawing process 】 about this article to review it.
Another point to make here is that the mHandler in mAttachInfo is actually a ViewRootHandler inside the ViewRootImpl.
Then call the DecorView dispatchAttachedToWindow, is actually the ViewGroup dispatchAttachedToWindow, usually related methods in ViewGroup, Call the child’s dispatchAttachedToWindow, pass AttachInfo in, and assign mAttachInfo to the child View.
// ViewGroup
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); }}// View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// ...
}
Copy the code
By this point, you might have forgotten what we were doing at the beginning.
We are looking at the flow of view.post. Let’s review the code for view.post:
// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
Copy the code
So now we know what attachInfo is, it’s the view wrootimpl that first triggered performTraversals, after performTraversals, View.post is handled by an internal ViewRootImpl Handler.
If executed before performTraversals or after mAttachInfo is left empty, it is processed through RunQueue.
GetRunQueue ().post(action); Did something.
The RunQueue here is actually the HandlerActionQueue.
So let’s look at the code for HandlerActionQueue.
public class HandlerActionQueue {
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; }}public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0; }}}Copy the code
GetRunQueue ().post(action); You actually add the code to mActions, save it, and then execute it at executeActions time.
There is only one executeActions call within dispatchAttachedToWindow(AttachInfo info, int visibility).
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if(mRunQueue ! =null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null; }}Copy the code
The difference between view. post and handler. post is:
- If the View. Post is called before performTraversals, the message is saved and then called by the Handler in ViewRootImpl at dispatchAttachedToWindow.
- If the View. Post is called after performTraversals, the call is made directly through the Handler in ViewRootImpl.
Why is the width and height of a View available in view. post? Since the View. Post Runnable has already been executed with performTraversals, the View’s measure Layout draw method has already been executed, so you can get the View’s width and height.
9. Memory leakage caused by Handler
This is an old question, which can be extended to the knowledge of memory leaks, such as: how to detect memory leaks, how to avoid memory leaks and so on.
10. Is it true that non-UI threads cannot manipulate views
One of the scenarios where we use Handler the most is when we use a Handler to manipulate the main View on a non-main thread. Is it true that a non-UI thread can’t manipulate a View? When performing UI operations, we will call the ViewRootImpl. For example, requestLayout will be checked by checkThread in requestLayout.
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mThread = Thread.currentThread();
}
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}void checkThread(a) {
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
When we look at this check, we don’t actually check for the main thread, we check for the mThread! CurrentThread, and mThread refers to the Thread created by ViewRootImpl. So it’s true that a non-UI thread can’t manipulate the View, but the check is that the thread being created is the current thread, and since the View wrootimPL was created on the main thread, manipulating the UI on the non-main thread won’t pass this check.
Third, summary
A small Handler can actually lead to a lot of problems. Here is a list of some problems that you may have overlooked. More problems are waiting for you to explore.
1. Basic principles of Handler
An illustration (from the Internet)
2. How to use Handler in child thread
- Looper.prepare Creates a Looper and adds it to ThreadLocal
- Looper.loop Starts the Looper loop
3. How does MessageQueue wait to get the message
Wait and wake up through the epoll mechanism.
4. Why use epoll instead of wait?
Before And after Android 2.2, Java wait/notify was used for waiting. After 2.3, epoll mechanism was used to process messages on the native side simultaneously.
5. Relationship between threads and Handler Looper MessageQueue
A thread for a Looper for a MessageQueue for multiple handlers.
6. How to ensure thread safety when multiple threads send messages to MessageQueue
Thread safety is ensured by locking MessageQueue.
7. How is the Handler message delay handled
- Convert the incoming delay time to the number of milliseconds from the boot time
- In MessageQueue, the order is sorted according to the time of the previous transformation
- When messagequeue.next gets the message, compare the current time (now) with the time of the first transformation (when). If now < when, wait through the timeout of epoll_wait
- If the message needs to wait, the Idel Handlers are executed, and then the handlers are checked to see if the message can be executed
View. Post and Handler.post
View.post is also used to execute messages via handler. post, as follows:
- If the View. Post is called before performTraversals, the message is saved and then called by the Handler in ViewRootImpl at dispatchAttachedToWindow.
- If the View. Post is called after performTraversals, the call is made directly through the Handler in ViewRootImpl.
9. Memory leakage caused by Handler
Skip over ~
10. Is it true that non-UI threads cannot manipulate views
Cannot operate because the ViewRootImpl checks to see if the thread that created the ViewRootImpl is the same as the thread that is currently operating on. The View wrootimPL is created on the main thread, so non-main threads cannot manipulate views.
The end of today’s article, I hope you can learn some different knowledge ~
The article is constantly updated. Search “ZYLAB” on wechat and get the update immediately. Reply to “Simulated interview” to unlock the one-to-one interview experience of Big factory
🏆 nuggets technical essay | double festival special articles