上一篇肥柴分析了Looper的线程唯一性原理,这一篇章,肥柴将从Looper的源码入手,对Looper获取分发消息原理的进一步解析,来解答以下三个问题:
1、Looper如何不断的获取Message并处理?
2、在UI线程里,系统预先为我们创建了一个Looper,那么UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?
3、当MessageQueue在检查到没有Message时进入了休眠,那如果此时,用户此时来一个按钮点击事件,点击事件包装成的Message被Handler扔进来时,MessageQueue又如何知道,从而立即做出反应?
以上三个问题具备层次关系,因此我们一步步来解答。
在Handler的使用中,我们一直强调需要调用Looper.loop()来启用消息循环监听处理,那么肥柴就从这部分源码入手,来回答第一个问题:Looper如何不断的获取Message并处理?
/** Looper.class */
@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed.");
}
me.mInLoop = true;
// other code ...
/** 注释1 循环获取Message */
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
/** 注释2 通过MessageQueue的next方法获取Message */
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// other code ...
try {
/** 注释3 获取到Message,分发并处理 */
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);
}
}
// other code ...
return true;
}
肥柴省略了大部分代码,只留下关键性源码,从中我们得知:loop()方法通过一个for (;;)死循环(注释1)不断的从MessageQueue中循环获取消息并处理,而具体的消息获取机制则在MessageQueue的next()中,如果取出的Message为null,则跳出循环;否则通过Message的target将消息传递给对应的Handler处理(注释3)。
肥柴有了如下结论:
1、Looper通过for (;;)死循环不断的从MessageQueue中循环获取消息并处理;
2、停止死循环的方法是使得Looper取得的消息为null,即调用quit()/quitSafely()方法;
在学习Handler使用的时候,我们经常会提到,主线程Looper的获取直接通过Looper.getMainLooper(),并不需要调用prepare()和loop()方法创建启用,原因在于:Android的主线程为ActivityThread,主线程的入口方法为main,在main方法中会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()开启主线程的消息循环。
/** ActivityThread.class */
public static void main(String[] args) {
// other code
Process.setArgV0("" );
Looper.prepareMainLooper();
// other code
ActivityThread thread = new ActivityThread();
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);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这时候就回到了我们的第二个问题:UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?
要回答这个问题,首先我们需要了解MessageQueue获取消息的源码。
/** MessageQueue.class */
@UnsupportedAppUsage
Message next() {
// 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();
}
/** 注释4 Message获取 */
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();
return msg;
}
} 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;
}
}
这里的源码相对来说比较长,大部分的操作是在计算消息延迟时间以及IdelHandler相关,这部分我们暂时不看,我们只关注注释4的地方,nativePollOnce()方法。
在MessageQueue中,nativePollOnce()方法检查到单链表里没有任何Message存在,就会使得线程阻塞在此处,loop()方法无法继续下面的处理逻辑,因此也没有可执行的代码片段,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程;如果检查到单链表队首有Message可以立即返回,则交给loop()执行接下来处理此Message的逻辑,处理完逻辑后。for(;;)又会紧接着循环到开头的queue.next(),如此周而复始…
因此对于第二个问题:UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?肥柴对以上的总结回答便是:在MessageQueue无消息情况下,nativePollOnce()方法阻塞线程,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程,因此并不会造成ANR。
进一步的,当MessageQueue在检查到没有Message时进入了休眠,那如果此时,用户此时来一个按钮点击事件,点击事件包装成的Message被Handler扔进来时,MessageQueue又如何知道,从而立即做出反应?
这里就涉及到了nativePollOnce()方法的机制原理。
实际上,从nativePollOnce的方法名就可以知道,这是一个JNI的底层方法,而这个方法便是使用了Linux的epoll机制。
epoll机制提供了Linux平台上最高效的I/O复用机制,它能够在一个地方等待多个文件句柄的I/O事件。简而言之就是Android使用pipe管道创建两个fd文件描述符,nativePollOnce方法中正是承载着这个管道的读操作,根据fd知道是管道读端有可读事件。
当有Message来临时,比如触摸事件,即会调用Message.enqueueMessage(),添加完Message后,调用了native层的nativeWake方法,就会向管道写端写一个“W”字符,这样就能触发管道读端从epoll_wait函数返回,从而达到唤醒线程的目的,从nativePollOnce()处继续执行下去。
至此,我们来总结回答开篇提到的三个问题,来结束这漫长的一篇:
1、Looper通过for (;;)死循环不断的从MessageQueue中循环获取消息并处理;
2、在MessageQueue无消息情况下,nativePollOnce()方法阻塞线程,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程,因此并不会造成ANR;
3、nativePollOnce()方法原理是利用Linux的epoll机制,以达到MessageQueue休眠情况下及时相应消息的到来和处理;
- - - - - Looper消息获取篇完 - - - - -