IdleHandler

源码

当 MessageQueue 没有消息的时候,就会阻塞在 next 方法中,其实在阻塞之前,MessageQueue 还会做一件事,就是检查是否存在 IdleHandler,如果有,就会去执行它的 queueIdle 方法。

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
  private IdleHandler[] mPendingIdleHandlers;  
Message next () {
int pendingIdleHandlerCount = -1;
for (; ; ) {
......
synchronized (this) {
//当消息执行完毕,就设置 pendingIdleHandlerCount
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages. when)) {
pendingIdleHandlerCount = mIdleHandlers.size ();
}
//初始化 mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max (pendingIdleHandlerCount, 4)];
}
//mIdleHandlers 转为数组
mPendingIdleHandlers = mIdleHandlers.toArray (mPendingIdleHandlers);
}

// 遍历数组,处理每个 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 {
keep = idler.queueIdle ();
} catch (Throwable t) {
Log.wtf (TAG, "IdleHandler threw exception", t);
}

//如果 queueIdle 方法返回 false,则处理完就删除这个 IdleHandler
if (! keep) {
synchronized (this) {
mIdleHandlers.remove (idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
}
}

当没有消息处理的时候,就会去处理这个 mIdleHandlers 集合里面的每个 IdleHandler 对象,并调用其 queueIdle 方法。最后根据 queueIdle 返回值判断是否用完删除当前的 IdleHandler。

然后看看 IdleHandler 是怎么加进去的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Looper.myQueue (). addIdleHandler (new IdleHandler () {  
@Override
public boolean queueIdle () {
//做事情
return false;
}
});

public void addIdleHandler (@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException ("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add (handler);
}
}

ok,综上所述,IdleHandler 就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。

系统应用

GC

GcIdler 方法理解起来很简单、就是获取上次 GC 的时间,判断是否需要 GC 操作。如果需要则进行 GC 操作。

何时会收到 GC_WHEN_IDLE 消息?当 AMS(ActivityManagerService) 中的这两个方法被调用之后:

  • doLowMemReportIfNeededLocked,这个方法看名字就知道是不够内存的时候调用的了,内存不足时,强行 GC
  • 当 ActivityThread 的 handleResumeActivity 方法被调用时。

onResume

[[3-Activity 生命周期#为什么 Activity.finish() 之后 10s 才 onDestroy ?]]

 onResume 方法执行完,界面已经显示这些更重要的事情已经处理完了,空闲的时候开始处理这些事情。也就是说系统的设计逻辑是保障最重要的逻辑先执行完,再去处理其他次要的事情。

但是如果 MessageQueue 队列中一直有消息,那么 IdleHandler 就一直没有机会被执行,那么原本该销毁的界面的 onStop,onDestory 就得不到执行吗?不是这样的,在 resumeTopActivityInnerLocked () -> completeResumeLocked () -> scheduleIdleTimeoutLocked () 方法中会发送一个会发送一个延迟消息(10s),如果界面很久没有关闭,耗时 (如果界面需要关闭),那么 10s 后该消息被触发就会关闭界面,执行 onStop 等方法。

第三方

LeakCanary
ensureGone () 中会进行 GC 回收和一些分析等操作,所以通过这些分析后,我们可以知道 LeakCanary 进行内存泄漏检测并不是 onDestry 方法执行完成后就进行垃圾回收和一些分析的,而是利用 IdleHandler 在空闲的时候进行这些操作的,尽量不去影响主线程的操作

使用场景

常见的使用场景有:启动优化。

我们一般会把一些事件(比如界面 view 的绘制、赋值)放到 onCreate 方法或者 onResume 方法中。但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。

所以我们可以把一些操作放到 IdleHandler 中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。

但是,这里需要注意下可能会有坑。可以用,如果没执行,需要优化代码

如果使用不当,IdleHandler 会一直不执行,比如在 View 的 onDraw 方法里面无限制的直接或者间接调用 View 的 invalidate 方法。

其原因就在于 onDraw 方法中执行 invalidate,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在 next 方法,而等到 FrameDisplayEventReceiver 异步任务之后又会执行 onDraw 方法,从而无限循环。

F332FEFC-81FE-466B-8716-67CF7A1A846B.png|900

其他使用场景:

  • 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用 View # post () 也能实现,区别就是前者会在消息队列空闲时执行
  • 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个 View,这也是种很酷的操作

IdleHandler
http://peiniwan.github.io/2024/04/c6c674473bb7.html
作者
六月的雨
发布于
2024年4月6日
许可协议