Simple understanding and use of IdleHandler
1. What is IdleHandler
To put it bluntly, IdleHandler is a mechanism provided by the Handler mechanism, which allows us to perform tasks when it is idle during the Looper event loop.
IdleHandler is defined in MessageQueue, which is an interface.
public final class MessageQueue {
public static interface IdleHandler {
boolean queueIdle();
}
}
- The return value is false, that is, it will only be executed once;
- The return value is true, that is, this method will be triggered every time there is no message that needs to be executed immediately in the message queue.
Basic usage
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.v(TAG, "#looper message has worked out");
return false;
}
});
}
2. How to use IdleHandler
2.1. Add and delete
In MessageQueue:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
mIdleHandlers
Manage all through a global variableIdleHandler
2.2. Execution
Since IdleHandler is mainly executed when the MessageQueue is idle, when will it be idle?
MessageQueue is a 消息触发时间
priority-based linked list, so there are two scenarios for idleness.
- MessageQueue is empty,
没有消息
; - The message that needs to be processed recently in MessageQueue is a
延迟消息
(when>currentTime)
, which needs to be executed in a delayed manner;
Both scenarios will try to execute IdleHandler.
To deal with the scene of IdleHandler, in Message.next()
this method of obtaining the next message to be executed in the message queue, let's follow the specific logic.
/** MessageQueue.class */
@UnsupportedAppUsage
Message next() {
// ...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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;
// 注释1 屏障消息处理,获取异步消息
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());
}
// 注释2 获取到Message不为null,则说明存在需要处理的Message
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.
// MessageQueue无消息,设置延迟为-1,nativePollOnce无限等待,直到有消息
nextPollTimeoutMillis = -1;
}
// 注释3 执行到此处,说明没有需要执行的Message(MessageQueue为空,或者存在延迟Message)
// 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();
}
// 注释4 IdleHandler队列为空,不执行,进入下一个循环,此后不再会执行IdleHandler判断,除非下次进入next方法
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)];
}
// 注释5 mIdleHandlers数组赋值给 mPendingIdleHandlers
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 注释6 执行IdleHandler队列中的空闲任务
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 {
// 注释7 执行任务逻辑,并返回任务释放移除
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
// 注释8 移除任务,下次不再执行
mIdleHandlers.remove(idler);
}
}
}
// 注释9 重置IdleHandler的Count为0,避免下次重复执行IdleHandler队列
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;
}
}
1. If the message is obtained in this cycle Message为空
, or the message is a 延时的消息
( now < mMessages.when
) and the specified trigger time has not yet arrived, then the current queue is determined to be 空闲状态
(at this timenextPollTimeoutMillis is -1)。
2. Traverse mPendingIdleHandlers
the array (the elements in this array will be mIdleHandlers
fetched every time) to call the method of each IdleHandler
instance queueIdle
.
- At that
pendingIdleHandlerCount < 0
time , it is the basis of the later cycle according tomIdleHandlers.size()
the assignment to
pendingIdleHandlerCount
mIdleHandlers
Copy the inIdleHandler
tomPendingIdleHandlers
the array , this array is temporary, and then enter the for loop;- Take it out from the array in the loop
IdleHandler
, and call itqueueIdle()
to record the return value and store itkeep
in ;
3. If this method returns false
, then this instance mIdleHandlers 中移除
will not continue to call back its queueIdle method when the queue is idle next time.
4. IdleHandler
After will nextPollTimeoutMillis
be set to 0, that isnon-blocking message queueOf course, it should be noted that the code executed here should not be too time-consuming, because it is executed synchronously. If it is too time-consuming, it will definitely affect the subsequent message execution.
3. Common problems and usage scenarios
3.1. Usage scenarios
According to the characteristics of IdleHandler, its usage scenarios follow a basic principle: 在不影响其他任务,在消息队列空闲状态下执行
.
For example: the GC scene of the Android Framework layer uses this mechanism, and only when the CPU is idle will it go to the GC:
After ApplicationThread
receiving GC_WHEN_IDLE
the message, scheduleGcIdler
the method will be triggered:
class H extends Handler {
......
public static final int GC_WHEN_IDLE = 120;
......
public void handleMessage(Message msg) {
switch (msg.what) {
......
case GC_WHEN_IDLE:
scheduleGcIdler();
......
}
}
}
In ActivityThread
the scheduleGcIdler
method ( ApplicationThread
it is ActivityThread
a non-static inner class, so the corresponding method can be called directly):
// 添加垃圾回收的IdleHandler
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
// 移除垃圾回收的IdleHandler
void unscheduleGcIdler() {
if (mGcIdlerScheduled) {
mGcIdlerScheduled = false;
Looper.myQueue().removeIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
final GcIdler mGcIdler = new GcIdler();
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
// 执行垃圾回收
BinderInternal.forceGc("bg");
}
}
Here, a custom IdleHandler
GC call is made, 线程空闲
which will be called when the time comes mGcIdler
, and finally BinderInternal.forceGc("bg")
the GC is triggered by the method. The minimum interval of this GC is 5 seconds ( MIN_TIME_BETWEEN_GCS
the value). And pay attention, mGcIdler
what is queueIdle
returned is false
, so this mGcIdler
will exist in the main thread for a long time MessageQueue
.
Other common scenarios: IdleHandler basic usage and application case analysis
3.2. Frequently asked questions
Q: IdleHandler is mainly executed when the MessageQueue is idle, so when will it be idle?
MessageQueue is a priority queue based on message trigger time, so there are two scenarios when the queue becomes idle.
MessageQueue 为空,没有消息;
MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;
Q: What is the use of IdleHandler?
IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
Q: MessageQueue provides add/remove IdleHandler methods, do they need to be used in pairs?
不是必须;
IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;
Q: Why doesn't it enter an infinite loop when mIdleHanders is not empty?
只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
Q: Can some unimportant startup services be moved to IdleHandler for processing?
不建议;
IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;
Q: Which thread does queueIdle() of IdleHandler run on?
陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;
reference
1. Interviewer: "It is written on your resume that you are familiar with the Handler mechanism, so let's talk about IdleHandler ?
" ) synchronization barrier with IdleHandler