• IdleHandler 原理浅析


    原文: https://www.cnblogs.com/mingfeng002/p/12091628.html

    IdleHandler:空闲处理器。

    在每次next获取消息进行处理时,发现没有可以处理的消息(队列空,只有延时消息并且没到时间,同步阻塞时没有异步消息)都会回调该接口。

    1.怎么使用?

    要使用IdleHandler只需要调用MessageQueue#addIdleHandler(IdleHandler handler)方法即

    Looper.myQueue().addIdleHandler(new IdleHandler() {
    
        @Override
        public boolean queueIdle() {
            ...
            return false;
        }
    }

     2.源码分析

    首先看下源码的解释

    复制代码
        /**
         * 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的queueIdle()方法,该方法返回一个boolean值:

    如果为false则执行完毕之后移除这条消息(一次性完事)

    如果为true则保留,等到下次空闲时会再次执行(重复执行)

    看MessageQueue的源码可以发现有两处关于IdleHandler的声明,分明是:

    • 存放IdleHandler的ArrayList(mIdleHandlers),
    • 还有一个IdleHandler数组(mPendingIdleHandlers)。

    后面的数组,它里面放的IdleHandler实例都是临时的,也就是每次使用完(调用了queueIdle方法)之后,都会置空(mPendingIdleHandlers[i] = null)

    下面看下MessageQueue的next()方法可以发现确实是这样

    复制代码
    Message next() {
            ......
            for (;;) {
                ......
         //调用本地方法,休眠

           nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
            // 此处为正常消息队列的处理
                    ......
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
                    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);
                }
    
                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);
                        }
                    }
                }
                pendingIdleHandlerCount = 0;
                nextPollTimeoutMillis = 0;
            }
        }
    复制代码

    就在MessageQueue的next方法里面。

    大概流程是这样的:

    1. 如果本次循环拿到的Message为空,或者!这个Message是一个延时的消息而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态,
    2. 接着就会遍历mPendingIdleHandlers数组(这个数组里面的元素每次都会到mIdleHandlers中去拿)来调用每一个IdleHandler实例的queueIdle方法,
    3. 如果这个方法返回false的话,那么这个实例就会从mIdleHandlers中移除,也就是当下次队列空闲的时候,不会继续回调它的queueIdle方法了。

    处理完IdleHandler后会将nextPollTimeoutMillis设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的message执行。

    能力大概就是上面讲的那样,那么能力决定用处,用处从本质上讲就是趁着消息队列空闲的时候干点事情,当然具体的用处还是要看具体的处理。

    3.来看看它在源码里的使用场景:

    比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。

    它在queueIdle方法被回调时,会做强行GC的操作(即调用BinderInternal的forceGc方法),但强行GC的前提是,与上一次强行GC至少相隔5秒以上。 

    ActivityThread中GcIdler

    那么我们就看看最为明显的ActivityThread中声明的GcIdler。在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中。具体如下:
    复制代码
     void scheduleGcIdler() {
            if (!mGcIdlerScheduled) {
                mGcIdlerScheduled = true;
                //添加GC任务
                Looper.myQueue().addIdleHandler(mGcIdler);
            }
            mH.removeMessages(H.GC_WHEN_IDLE);
        }
    复制代码

    ActivityThread中GcIdler的详细声明:

    复制代码
       //GC任务
       final class GcIdler implements MessageQueue.IdleHandler {
            @Override
            public final boolean queueIdle() {
                doGcIfNeeded();
                //执行后,就直接删除
                return false;
            }
        }
        // 判断是否需要执行垃圾回收。
        void doGcIfNeeded() {
            mGcIdlerScheduled = false;
            final long now = SystemClock.uptimeMillis();
            //获取上次GC的时间
            if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
                //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
                BinderInternal.forceGc("bg");
            }
        }
    复制代码

    GcIdler方法理解起来很简单、就是获取上次GC的时间,判断是否需要GC操作。如果需要则进行GC操作。这里ActivityThread中还声明了其他空闲时的任务。如果大家对其他空闲任务感兴趣,可以自行研究。

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

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

    何时会收到GC_WHEN_IDLE消息?

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

    • doLowMemReportIfNeededLocked, 这个方法看名字就知道是不够内存的时候调用的了。
    • activityIdle, 这个方法呢,就是当ActivityThread的handleResumeActivity方法被调用时(Activity的onResume方法也是在这方法里回调)调用的。

    5.其他可以想到的场景

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

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

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

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

    附:

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

  • 相关阅读:
    #include< >和#include“ ”的区别
    第2章 基础语法 -- 运算符
    hive on spark的安装及问题
    第2章 Python基础语法 -- 数据类型
    第2章 Python基础语法--Hello python
    IT资源专业搜索-www.easysoo.cn
    学习英语
    zookeeper 安装
    Hadoop2.6 Ha 安装
    Hadoop平台安装前准备
  • 原文地址:https://www.cnblogs.com/wytiger/p/13030199.html
Copyright © 2020-2023  润新知