可以看到,loop 方法中存在一个for(;;)
死循环,如果该方法中 queue.next()
方法返回 null ,那么直接 return 退出整个死循环,整个ActivityThread.main()
方法也就结束了,整个程序也就退出了。但是我们的程序肯定是一直在运行的,也就是说 queue.next()
方法中一直有消息,但是如果一段时间没有操作了,整个程序也就没有执行的消息了,那为什么程序还能一直运行呢,所以问题肯定就在 queue.next()
这个方法中。
该方法中也有一个 for(;;)
死循环,里面有一个关键方法 nativePollOnce
@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.
···
int nextPollTimeoutMillis = 0;
for (;😉 {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
···
nativePollOnce(ptr, nextPollTimeoutMillis); //没有消息,阻塞等待
···
}
}
该方法在 nextPollTimeoutMillis = -1
的时候就阻塞等待,直到下一条消息可用为止。否则就继续向下执行。那我们再看看是在哪里唤醒的呢?是在消息入队最终执行的方法 enqueueMessage 中:
boolean enqueueMessage(Message msg, long when) {
···
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在 nativeWake 方法中进行唤醒,就是唤醒上面的那个地方,没有消息的时候,这里就处于阻塞状态。
这样我们把消息处理机制的整个逻辑大概梳理了一下,为什么需要理清呢,因为 IdleHandler 是在消息队列没有消息或者是在有暂时不需要处理的消息(延迟消息),就是说这个时候是空闲的,进行 IdleHandler 进行处理的。所以我们可以猜测 IdleHandler 应该也在 next 方法中进行触发它的方法。事实也确实如此:
Message next() {
…
for (;😉 {
…
synchronized (this) {
// 此处为正常消息队列的处理
…
if (mQuitting) {
dispose();
return null;
}
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)];
}
//mIdleHandlers 数组,赋值给 mPendingIdleHandlers
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;
}
}
看 MessageQueue 的源码可以发现有两处关于 IdleHandler 的声明,分别是:
-
存放 IdleHandler 的 ArrayList(mIdleHandlers),
-
还有一个 IdleHandler 数组 (mPendingIdleHandlers)。
后面的数组,它里面放的 IdleHandler 实例都是临时的,也就是每次使用完(调用了queueIdle 方法)之后,都会置空(mPendingIdleHandlers[i] = null)
,在 MessageQueue 的 next() 方法中
大致的流程是这样的:
-
如果本次循环拿到的 Message 为空,或者这个 Message 是一个延时的消息而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态。
-
接着就会遍历 mPendingIdleHandlers 数组(这个数组里面的元素每次都会到 mIdleHandlers 中去拿)来调用每一个IdleHandler 实例的 queueIdle 方法。
-
如果这个方法返回false的话,那么这个实例就会从 mIdleHandlers 中移除,也就是当下次队列空闲的时候,不会继续回调它的 queueIdle 方法了。
处理完 IdleHandler 后会将 nextPollTimeoutMillis 设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的 message 执行。
系统源码中的使用
知道了这个 IdleHandler 是如何被触发的,我们再来看看系统源码时如何使用它的:比如 ActivityThread.Idle 在 ActivityThread.handleResumeActivity()
中调用。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
···
//该方法最终会执行 onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn’t actually resume the activity, so skipping any follow-up actions.
return;
}
···
···
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
可以看到在 handleResumeActivity() 方法中末尾会执行 Looper.myQueue().addIdleHandler(new Idler())
,也就是说在 onResume 等方法都执行完,界面已经显示出来了,那么这个 Idler 是用来干嘛的呢?先来看看这个内部类的定义:
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
···
if (a != null) {
mNewActivities = null;
IActivityManager am = ActivityManager.getService();
ActivityClientRecord prev;
do {
//打印一些日志
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null && a.activity.mFinished));
if (a.activity != null && !a.activity.mFinished) {
try {
//AMS 进行一些资源的回收
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
if (stopProfiling) {
mProfiler.stopProfiling();
}
//确认Jit 可以使用,否则抛出异常
ensureJitEnabled();
return false;
}
}
可以看到在 queueIdle 方法中会进行回收等操作,下面会详细讲解,但这一些都是等 onResume 方法执行完,界面已经显示这些更重要的事情已经处理完了,空闲的时候开始处理这些事情。也就是说系统的设计逻辑是保障最重要的逻辑先执行完,再去处理其他次要的事情。
但是如果 MessageQueue 队列中一直有消息,那么 IdleHandler 就一直没有机会被执行,那么原本该销毁的界面的 onStop,onDestory 就得不到执行吗?不是这样的,在 resumeTopActivityInnerLocked() -> completeResumeLocked() -> scheduleIdleTimeoutLocked()
方法中会发送一个会发送一个延迟消息(10s),如果界面很久没有关闭(如果界面需要关闭),那么 10s 后该消息被触发就会关闭界面,执行 onStop 等方法。
常见使用方式
-
在应用启动时我们可能希望把一些优先级没那么高的操作延迟一点处理,一般会使用 Handler.postDelayed(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。所以在做项目性能优化的时候可以使用 IdleHandler,它在主线程空闲时执行任务,而不影响其他任务的执行。
-
想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行
-
发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
第三方库的使用
LeakCanary(1.5源码)
我们来看看 LeakCanary 的使用,是在AndroidWatchExecutor 这个类中
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = “LeakCanary-Heap-Dump”;
private final Handler mainHandler;
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
//初始调用
@Override public void execute(Retryable retryable) {
// 最终都会切换到主线程,调用waitForIdle()方法
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
尾声
在我的博客上很多朋友都在给我留言,需要一些系统的面试高频题目。之前说过我的复习范围无非是个人技术博客还有整理的笔记,考虑到笔记是手写版不利于保存,所以打算重新整理并放到网上,时间原因这里先列出面试问题,题解详见:
展示学习笔记
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
系统的面试高频题目。之前说过我的复习范围无非是个人技术博客还有整理的笔记,考虑到笔记是手写版不利于保存,所以打算重新整理并放到网上,时间原因这里先列出面试问题,题解详见:
[外链图片转存中…(img-I4syyf4A-1714501980224)]
展示学习笔记
[外链图片转存中…(img-1Sc1gOu5-1714501980225)]
[外链图片转存中…(img-c0boseqk-1714501980226)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!