一、IdleHandler是什么?怎么使用?
IdleHandler就是Handler机制提供的一种可以在Looper事件循环的过程中,当出现空闲的时候,允许我们去执行任务的一种机制。
IdleHandler被定义在MessageQueue中,它是一个接口:
// MessageQueue.java
public static interface IdleHandler {
boolean queueIdle();
}
可以看到,定义时需要实现其queueIdle()方法,同时返回值为true表示是一个持久的IdleHandler,会重复使用;返回false表示是一个一次性的IdleHandler。
在MessageQueue中还定义了对应的add和remove方法:
// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
// ...
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
可以看到 add 或 remove其实操作的都是mIdleHandlers,它的类型是一个ArrayList:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
既然IdleHandler主要是在MessageQueue出现空闲的时候被执行的,那么何时会出现空闲?
- MessageQueue维护的消息队列为空,没有消息。
- MessageQueue中最近要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行。
这两个场景都会尝试执行IdleHandler。
处理IdleHandler的场景,就在Message.next()这个获取消息队列下一个待执行消息的方法中:
Message next() {
// ...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ...
if (msg != null) {
if (now < msg.when) {
// 计算休眠的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Other code
// 找到消息处理后返回
return msg;
}
} else {
// 没有更多的消息
nextPollTimeoutMillis = -1;
}
//执行到这里说明,要不消息队列为空,要么待处理的消息是延迟消息(因为如果有需要执行的非延迟消息的话,在上面 return msg 处就已经找到该消息做返回了,就不会执行到这里)
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//这里判断如果有待处理的IdleHandler的数量为空,就不会往下执行了,会跳过本次循环。
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//这里keep的值取值于idler.queueIdle(),即回调方法中的返回值,如果为false,则在从集合里移除掉这个IdleHandler,如果为true,则忽略。(这里正说明了,queueIdle的返回值如果是false,那么就是一次性的,如果为true,那么在每次空闲时都会回调queueIdle)
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
我们先解释一下next()中关于 IdleHandler执行的主逻辑:
(1)准备执行IdleHandler时,说明当前待执行的消息为null,或者这条消息的执行时间未到;
(2)当pendingIdleHandlerCout<0时,根据mIdleHandlers.size()赋值给pendingIdleHandlerCount,它是后期循环的基础;
(3)将mIdleHandlers中的IdleHandler拷贝到mPendingIdleHandlers数组中,这个数组是临时的,之后进入for循环。
(4)循环从数组取出IdleHandler,并调用其queueIdle()记录返回值存到keep中;
(5)当keep为false时,从mIdleHandler中移除当前循环的IdleHandler,反之则保留;
需要注意的是,对mIdleHandler的操作,都通过synchronized来保证线程安全,这一点无需担心。
二、IdleHandler是如何保证不进入死循环的?
当队列空闲时,会循环执行一遍mIdleHandlers数组并执行IdleHandler.queueIdle()方法。而如果数组中有一些IdleHandler的queueIdle()方法返回了true,则会保留在mIdleHandlers数组中,下次依然会再执行一遍。
注意现在代码逻辑还在MessageQueue.next()的循环中,在这个场景下IdleHandler机制是如何保证不会进入死循环的?
实际不会死循环的关键是在于pendingIdleHandlerCount,我们看看下面的代码:
Message next() {
// ...
// Step 1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ...
// Step 2
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// Step 3
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// ...
}
// Step 4
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
我们梳理一下:
- Step1,循环开始前,pendingIdleHandlerCount 的初始值为-1;
- Step2,在pendingIdleHandlerCount<0时,才会通过mIdleHandlers.size()赋值。也就是说只有第一次循环才会改变pendingIdleHandlerCount的值;
- Step3,如果pendingIdleHandlerCount<=0时,则循环continue;
- Step4,重置pendingIdleHandlerCount 为0;
在第二次循环时,pendingIdleHandlerCount等于0,在Step2不会改变它的值,那么在Step3中会直接continue继续下一次循环,此时没有机会修改nextPollTimeoutMillis。
那么nextPollTimeoutMillis的值有两种可能:-1 或者 下次唤醒的等待时间间隔,在执行到nativePollOnce()时就会进入休眠,等待再次被唤醒。
下次唤醒时,mMessage必然会有一个待执行的Message,则MessageQueue.next() 返回到Looper.loop()的循环中,分发处理这个Message,之后又是新一轮的next()中去循环。
三、系统中有哪些地方会用到IdleHandler机制?
在AS中搜一下IdleHandler:
- ActivityThread.Idler 在ActivityThread.handleResumeActivity()中调用
- ActivityThread.GcIdler 是在内存不足时,强行GC
- Instrumentation.ActivityGoing 在Activity onCreate()执行前添加;
- Instrumentation.Idler 调用的时机就比较多了,是键盘相关的调用
- TextToSpeechService.SynthThread 是在TTS合成完成之后发送广播
四、一些面试问题
- Q:IdleHandler有什么作用?
(1)IdleHandler 是Handler提供的一种在消息队列空闲时,执行任务的时机;
(2)当MessageQueue 没有立即需要处理的消息时,会执行IdleHandler - Q:MessageQueue 提供了 add/remove IdleHandler的方法,是否需要成对使用?
(1)不是必须
(2)IdleHandler.queueIdle()的返回值,可以移除加入MessageQueue的IdleHandler
3、Q:当mIdleHandlers一直不为空时,为什么不会进入死循环?
(1)只有在pendingIdleHandlerCount 为-1时,才会尝试执行mIdleHandler;
(2)pedingIdleHandlerCount在next() 中初始时为-1,执行一遍后会被置为0,所以不会重复执行。
4、Q:是否可以将一些不重要的启动服务,搬移到IdleHandler中去处理?
(1)不建议;
(2)IdleHandler的处理时机不可控,如果MessageQueue中一直有待处理的消息,那么IdleHandler的执行时机会很靠后。
5、IdleHandler的queueIdle()运行在哪个线程?
(1)queueIdle() 运行的线程,只和当前MessageQueue的Looper所在的线程有关;
(2)子线程一样可以构造Looper,并添加IdleHandler;
五、小结
IdleHandler是Handler提供的一种在消息队列空闲时,执行任务的时机。但它执行的时机依赖消息队列的情况,那么如果MessageQueue 一直有待执行的消息时,IdleHandler就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。
IdleHandler 在API上面的解释如下:
public final void addIdleHandler (MessageQueue.IdleHandler handler)
向消息队列中添加一个新的MessageQueue.IdleHandler。当调用IdleHandler.queueIdle()返回false时,此MessageQueue.IdleHandler会自动的从消息队列中移除。或者调用removeIdleHandler(MessageQueue.IdleHandler)也可以从消息队列中移除MessageQueue.IdleHandler。
此方法是线程安全的。
注意:在主线程中使用时queueIdle中不能执行太耗时的任务。
六、使用场景
1、用在Android初始化Activity界面时。比如,想用Android做一个播放器,如果下面包括进度条,暂停、停止等按钮的空间用PopWindow实现的话。就是程序一运行起来就需要将下面的PopupWindow显示在Activity上。用这个是比较好的,当然你也可以用Handler的sendEmptyMessage()去做你想要的操作。
把IdleHandler用在onCreate()里面,用法很简单:
Looper.myQueue().addIdleHandler(new IdleHandler()
{
@Override
public boolean queueIdle()
{
// TODO Auto-generated method stub
//你想做的任何事情
//........
//........
return false;
}
});
2、在Android项目中都会遇到希望一些操作延迟一点的处理,一般会使用Handler.postDelayed(Runnable r,long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能差可能需要延迟较多,有的性能好可以允许较少的延迟时间。
对于多个任务的延迟加载,如果addIdleHandler()调用多次明显不太优雅,而且也不要把所有要延迟的任务都一起放到queueIdle()方法内。根据queueIdle()返回true时可以执行多次的特点,也可以实现一个任务列表,然后从这个任务列表中取任务执行。
下面给出具体实现方案:
import android.os.Looper;
import android.os.MessageQueue;
import java.util.LinkedList;
import java.util.Queue;
public class DelayTaskDispatcher {
private Queue<Task> delayTasks = new LinkedList<>();
private MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if (delayTasks.size() > 0) {
Task task = delayTasks.poll();
if (task != null) {
task.run();
}
}
return !delayTasks.isEmpty(); //delayTasks非空时返回ture表示下次继续执行,为空时返回false系统会移除该IdleHandler不再执行
}
};
public DelayTaskDispatcher addTask(Task task) {
delayTasks.add(task);
return this;
}
public void start() {
Looper.myQueue().addIdleHandler(idleHandler);
}
}
//使用系统Runnable接口自定义Task接口
public interface Task extends Runnable {
}
使用方法:
new DelayTaskDispatcher().addTask(new Task() {
@Override
public void run() {
Log.d(TAG, "DelayTaskDispatcher one task");
}
}).addTask(new Task() {
@Override
public void run() {
Log.d(TAG, "DelayTaskDispatcher two task");
}
}).start();
使用上述方式可以添加多个任务,在线程空闲时分别执行。
3、IdleHandler它在源码中的使用场景
比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。
它在queueIdle方法被回调时,会做强行的GC操作(即调用BinderInternal的forceGc方法),但强行GC的前提是,与上一次强行GC至少相隔5秒以上。
在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中。具体如下:
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
//添加GC任务
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
ActivityThread中GcIdler的详细声明:
//GC任务
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
//执行后,就直接删除
return false;
}
}
// 判断是否需要执行垃圾回收。
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//获取上次GC的时间
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
其他使用场景:
- Activity启动优化(加快App启动速度):onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
- 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
- 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
- 一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看