【Android面试题】Android Framework——IdleHandler是什么?怎么使用,能解决什么问题?

IdleHandler是什么?怎么使用,能解决什么问题?

这道题想考察什么?

这道题想考察同学对Handler的 IdleHandler 的理解。

考生应该如何回答

IdleHandler 是 MessageQueue内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的 message时,会主动触发 IdleHandler 的 queueIdle方法。返回值为 false,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。具体的细节说明如下:

IdleHandler 是 闲时Handler 机制,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务。
IdleHandler 被定义在 MessageQueue 中,它是一个接口。

// MessageQueue.java
public static interface IdleHandler {
    boolean queueIdle();
}

可以看出,定义时需要实现其 queueIdle() 方法。同时返回值为 true 表示重复使用 IdleHandler,返回 false 表示是一个一次性的 。
既然 IdleHandler 被定义在 MessageQueue 中,使用它也需要借用 MessageQueue。在 MessageQueue 中申明了对应的 add 和 remove 方法。

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
    // ...
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

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. 循环中从mPendingIdleHandlers数组中取出 IdleHandler,并执行其 queueIdle() 记录返回值存到 keep 中;
  5. 当 keep 为 false 时,从 mIdleHandler 中移除当前循环的 IdleHandler,反之则保留;

可以看到 IdleHandler 机制中,最重要的就是在 next() 中,如果messageQueue队列空闲(没有消息需要立刻处理),那么就循环 mIdleHandler 中的 IdleHandler 对象,如果其 queueIdle() 返回为 false 时,就会从 mIdleHander 中移除。
需要特别注意的是,对 mIdleHandler 这个 List 的所有操作,都是通过 synchronized 来保证线程安全的。

一些关于IdleHandler的细节

IdleHandler 可以是可以保证不会进入死循环。
当队列空闲时,会循环调用一遍 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 时,则循环 continue;
  • Step 4,重置 pendingIdleHandlerCount 为 0;

当第一次循环时,pendingIdleHandlerCount<0,会给pendingIdleHandlerCount赋值,然后执行到Step4,此时 Step3不会执行,当第二次循环时,pendingIdleHandlerCount 等于 0,在 Step 2 不会改变它的值,那么在 Step 3 中会直接 continus 继续会下一次循环,此时没有机会修改 nextPollTimeoutMillis。
那么 nextPollTimeoutMillis 有两种可能:-1 或者下次唤醒的等待间隔时间,在执行到 nativePollOnce() 时则会进入休眠,等待下一次被唤醒。
下次唤醒的时候,mMessage 必然会有一个等待执行的 Message,则 MessageQueue.next() 返回到 Looper.loop() 的循环中,分发处理这个 Message,之后又是一轮新的 next() 中去循环。

IdleHandler能解决什么问题?

通过上面的分析我们不难发现,IdleHandler是在主线程空闲的时候被执行。因此基于这个点,我们可以将一些相对耗时或者一些影响整个线程运行的事务放到IdleHandler里面处理,譬如当我们需要收到调用GC的时候,GC一般会带来STW问题,于是我们可以将这个动作在IdleHandler里面执行,而android源码确实也是这样进行的。

最后

我整理了一套Android面试题合集,除了以上面试题,还包含【Java 基础、集合、多线程、虚拟机、反射、泛型、并发编程、Android四大组件、异步任务和消息机制、UI绘制、性能调优、SDN、第三方框架、设计模式、Kotlin、计算机网络、系统启动流程、Dart、Flutter、算法和数据结构、NDK、H.264、H.265.音频编解码、FFmpeg、OpenMax、OpenCV、OpenGL ES
在这里插入图片描述

有需要的朋友可以扫描下方二维码,免费领取全部面试题+答案解析!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值