面试官:“看你简历上写熟悉 Handler 机制,那聊聊 IdleHandler 吧?” (2)

可以看到 IdleHandler 机制中,最核心的就是在 next() 中,当队列空闲的时候,循环 mIdleHandler 中记录的 IdleHandler 对象,如果其 queueIdle() 返回值为 false 时,将其从 mIdleHander 中移除。

需要注意的是,对 mIdleHandler 这个 List 的所有操作,都通过 synchronized 来保证线程安全,这一点无需担心。

2.3 IdleHander 是如何保证不进入死循环的?

当队列空闲时,会循环执行一遍 mIdleHandlers 数组并执行 IdleHandler.queueIdle() 方法。而如果数组中有一些 IdleHander 的 queueIdle() 返回了 true,则会保留在 mIdleHanders 数组中,下次依然会再执行一遍。

注意现在代码逻辑还在 MessageQueue.next() 的循环中,在这个场景下 IdleHandler 机制是如何保证不会进入死循环的?

有些文章会说 IdleHandler 不会死循环,是因为下次循环调用了 nativePollOnce() 借助 epoll 机制进入休眠状态,下次有新消息入队的时候会重新唤醒,但这是不对的。

注意看前面 next() 中的代码,在方法的末尾会重置 pendingIdleHandlerCount 和 nextPollTimeoutMillis。

Message next() { // ... int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); // ... // 循环执行 mIdleHandlers // ... pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } }

nextPollTimeoutMillis 决定了下次进入 nativePollOnce() 超时的时间,它传递 0 的时候等于不会进入休眠,所以说 natievPollOnce() 进入休眠所以不会死循环是不对的。

这很好理解,毕竟 IdleHandler.queueIdle() 运行在主线程,它执行的时间是不可控的,那么 MessageQueue 中的消息情况可能会变化,所以需要再处理一遍。

实际不会死循环的关键是在于 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; } }

我们梳理一下:

Step 1,循环开始前,pendingIdleHandlerCount 的初始值为 -1;

Step 2,在 pendingIdleHandlerCount<0 时,才会通过 mIdleHandlers.size() 赋值。也就是说只有第一次循环才会改变 pendingIdleHandlerCount 的值;

Step 3,如果 pendingIdleHandlerCount<=0 时,则循环 continus;

Step 4,重置 pendingIdleHandlerCount 为 0;

在第二次循环时,pendingIdleHandlerCount 等于 0,在 Step 2 不会改变它的值,那么在 Step 3 中会直接 continus 继续下一次循环,此时没有机会修改 nextPollTimeoutMillis。

那么 nextPollTimeoutMillis 有两种可能:-1 或者下次唤醒的等待间隔时间,在执行到 nativePollOnce() 时就会进入休眠,等待再次被唤醒。

下次唤醒时,mMessage 必然会有一个待执行的 Message,则 MessageQueue.next() 返回到 Looper.loop() 的循环中,分发处理这个 Message,之后又是一轮新的 next() 中去循环。

2.4 framework 中如何使用 IdleHander?

到这里基本上就讲清楚 IdleHandler 如何使用以及一些细节,接下来我们来看看,在系统中,有哪些地方会用到 IdleHandler 机制。

在 AS 中搜索一下 IdleHandler。

简单解释一下:

ActivityThread.Idler 在 ActivityThread.handleResumeActivity() 中调用。

ActivityThread.GcIdler 是在内存不足时,强行 GC;

Instrumentation.ActivityGoing 在 Activity onCreate() 执行前添加;

Instrumentation.Idler 调用的时机就比较多了,是键盘相关的调用;

TextToSpeechService.SynthThread 是在 TTS 合成完成之后发送广播;

有兴趣可以自己追一下源码,这些都是使用的场景,具体用 IdleHander 干什么,还是要看业务。

三.一些面试问题

到这里我们就讲清楚 IdleHandler 干什么?怎么用?有什么问题?以及使用中一些原理的讲解。

下面准备一些基本的问题,供大家理解。

Q:IdleHandler 有什么用?

IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;

当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;

Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?

不是必须;

IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;

Q:当 mIdleHanders 一直不为空时,为什么不会进入死循环?

只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;

pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

Q:是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?

不建议;

IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;

Q:IdleHandler 的 queueIdle() 运行在那个线程?

陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;

子线程一样可以构造 Looper,并添加 IdleHandler;

三. 小结时刻

到这里就把 IdleHandler 的使用和原理说清除了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpppgz.html