[TOC]

概述

Handler中的IdleHandler到底是个什么东西??这个是面试经常问的。它有什么能力?它有什么用处?能想到一些合适的场景吗?

IdleHandler 可以用来提升性能,主要用在我们希望能够在当前线程 消息队列空闲时 做些事情(例如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。

首先看下源码的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
* 可以理解为消息暂时处理完的适合回调的
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}

这个IdleHandler,它是声明在MessageQueue里面的一个接口,所以我们可以猜想到它跟MessageQueue一定有关系,这个接口只有一个方法:queueIdle,字面意思就是队列空闲。 看MessageQueue的源码可以发现有两处关于IdleHandler的声明,分别是:

我们可以看到,IdleHandler是个接口。注释中明确的指出当消息队列空闲时会执行IdelHandler的queueIdle()方法,该方法返回一个boolean值, 如果为false则执行完毕之后移除这条消息, 如果为true则保留,等到下次空闲时会再次执行,下面看下MessageQueue的next()方法可以发现确实是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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();
}
//
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);
}
}
}
// 处理完IdleHandler后会将nextPollTimeoutMillis设置为0,也就是不阻塞消息队列,
// 当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的message执行。
// 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;
}
}

处理完IdleHandler后会将nextPollTimeoutMillis设置为0,也就是不阻塞消息队列, 当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的message执行。 能力大概就是上面讲的这样,那么能力决定用处,用处从本质上讲就是趁着消息队列空闲的时候干点事情,当然具体用处还是要看具体的处理。 要使用IdleHandler只需要调用MessageQueue#addIdleHandler(IdleHandler handler)方法即可

合适场景可以从下面几点出发:

  • 消息队列相关
  • 主线程能干的事情
  • 返回true和false带来不同结果

目前可以想到的场景:

  • Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间

  • 想要一个View绘制完成之后添加其他依赖于这个View的View,当然这个View#post()也能实现,区别就是前者会在消息队列空闲时执行。

  • 发生一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作。

  • 一些第三方库中使用,比如LeakCanary,Glide中使用到,具体可以自行去查看。

逻辑流程

IdleHandler数组里面放的IdleHandler实例都是临时的,也就是每次使用完(调用了queueIdle方法)之后,都会置空(mPendingIdleHandlers[i]=null) 那它们会在什么时候用到呢? 就是在MessageQueue的next方法里面。

如果本次循环拿到的Message为空,或者!这个Message是一个延时的消息而且还没到指定触发时间,那么就认定当前的队列为空闲时间。

接着会遍历mPendingIdleHandlers数组(这个数组里面的元素每次都会到mIdleHandlers中去拿)来调用每一个IdleHandler实例的queueIdle方法。

如果这个方法返回false的话,那么这个实例就会从mIdleHandlers中移除,也就是当下次队列空闲的时候,不会继续回调它的queueIdle方法了。

使用场景

比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。 它在queueIdle方法被回调时,会做强行GC的操作(即调用BinderInternal的faceGc方法),但强行GC的前提是与上一次强行GC至少相隔5秒以上。

我们看一下他的源码:

1
2
3
4
5
6
7
8
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
purgePendingResources();
return false;
}
}

我们看到他是一个实现了MessageQueue.IdleHandler的接口,并且实现了queueIdle,我们可以看一下这个方法的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;

void doGcIfNeeded() {
doGcIfNeeded("bg");
}


void doGcIfNeeded(String reason) {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
// + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc(reason);
}
}

我们可以看一下。这里面其实他会强制执行gc,即调用BinderInternal的faceGc方法。执行gc的时间间隔是5秒钟。

1
2
3
4
5
6
7
8
9
10
11
/**
*
*/
private void purgePendingResources() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
nPurgePendingResources();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}

// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();

这个方法会调用nPurgePendingResources。这个是一个jni方法.至于这个方法是干嘛的,我们下次再看。

那这个GcIdler会在什么时候使用呢?

当ActivityThread的mH(Handler)收到GC_WHEN_IDLE消息之后。

1
2
3
4
//当ActivityThread的mH(Handler)收到GC_WHEN_IDLE消息之后。会调用GcIdler()
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
1
2
3
4
5
6
7
8
@UnsupportedAppUsage
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}

我们看到他往主线程的Handler里面加入了IdleHandler对象。很明显,我们大概可以知道这个时候就是当主线程相对比较空闲的时候,会添加IdleHandler然后进行空闲时候的一些GC操作。

那么,我们可以思考,时候时候主线程会发送GC_WHEN_IDLE这个消息呢??

何时会收到GC_WHEN_IDLE消息?

当AMS(ActivityManagerService)中的这两个方法被调用之后:

doLowMemReportIfNeededLocked,这个方法看名字就知道是不够内存的时候调用了。

activityIdle,这个方法呢,就是当ActivityThread的handleResumeActivity方法被调用时(Activity的onResume方法也是在这方法里面回调)调用的。