写点什么

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

用户头像
Android架构
关注
发布于: 11 小时前

}


}


public void removeIdleHandler(@NonNull IdleHandler handler) {


synchronized (this) {


mIdleHandlers.remove(handler);


}


}


可以看到 add 或 remove 其实操作的都是 mIdleHandlers,它的类型是一个 ArrayList。


既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么何时出现空闲?


MessageQueue 是一个基于消息触发时间的优先级队列,所以队列出现空闲存在两种场景。


  1. MessageQueue 为空,没有消息;

  2. 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;


}


if (pendingIdleHandlerCount < 0


&& (mMessages == null || now < mMessages.when)) {


pendingIdleHandlerCount = mIdleHandlers.size();


}


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);


}


if (!keep) {


synchronized (this) {


mIdleHandlers.remove(idler);


}


}


}


pendingIdleHandlerCount = 0;


nextPollTimeoutMillis = 0;


}


}


我们先解释一下 next() 中关于 IdleHandler 执行的主逻辑:


  1. 准备执行 IdleHandler 时,说明当前待执行的消息为 null,或者这条消息的执行时间未到;

  2. pendingIdleHandlerCount < 0 时,根据 mIdleHandlers.size() 赋值给 pendingIdleHandlerCount,它是后期循环的基础;

  3. mIdleHandlers 中的 IdleHandler 拷贝到 mPendingIdleHandlers 数组中,这个数组是临时的,之后进入 for 循环;

  4. 循环中从数组中取出 IdleHandler,并调用其 queueIdle() 记录返回值存到 keep 中;

  5. keep 为 false 时,从 mIdleHandler 中移除当前循环的 IdleHandler,反之则保留;


可以看到 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 pendingIdleH


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


andlerCount = -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。



简单解释一下:


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

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

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

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

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


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


三.一些面试问题




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


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


Q:IdleHandler 有什么用?


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

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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
面试官:“看你简历上写熟悉 Handler 机制,那聊聊 IdleHandler 吧