Android IdleHandler 原理浅析

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 (;;) {
            ......
            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.IdleHandler返回true和false带来的不同结果

queueIdle() 方法如果返回 false,那么这个 IdleHandler 只会执行一次,执行完后会从队列中删除,如果返回 true,那么执行完后不会被删除,只要执行 MessageQueue.next 时消息队列中没有可执行的消息,即为空闲时间,那么 IdleHandler 队列中的 IdleHandler 还会继续被执行。

public void clickTestIdleHandler(View view) {
    // 添加第一个 IdleHandler
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","IdleHandler1 queueIdle");
            return false;
        }
    });
    // 添加第二个 IdleHandler
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","IdleHandler2 queueIdle");
            return false;
        }
    });
    Handler handler = new Handler();
    // 添加第一个Message
    Message message1 = Message.obtain(handler, new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","message1");
        }
    });
    message1.sendToTarget();
    // 添加第二个Message
    Message message2 = Message.obtain(handler, new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","message2");
        }
    });
    message2.sendToTarget();
}

上面的例子中分别向消息队列中添加来两个 IdleHandler 和两个 Message,其中 IdleHandler 的 queueIdle() 方法返回 false,下面来看一下结果:

16:23:07.726 E/Test: message1
16:23:09.727 E/Test: message2
16:23:11.732 E/Test: IdleHandler1 queueIdle
16:23:13.733 E/Test: IdleHandler2 queueIdle

可以看到 IdleHandler 是在消息队列的其它消息执行完后才执行的,而且只执行来一次。
再来看一下 queueIdle() 方法返回 true 的情况:

16:27:02.083  E/Test: message1
16:27:04.084  E/Test: message2
16:27:06.090  E/Test: IdleHandler1 queueIdle
16:27:08.090  E/Test: IdleHandler2 queueIdle
16:27:10.095  E/Test: IdleHandler1 queueIdle
16:27:12.096  E/Test: IdleHandler2 queueIdle
16:27:14.099  E/Test: IdleHandler1 queueIdle
16:27:16.099  E/Test: IdleHandler2 queueIdle
16:27:43.788  E/Test: IdleHandler1 queueIdle
16:27:45.788  E/Test: IdleHandler2 queueIdle
16:27:47.792  E/Test: IdleHandler1 queueIdle
16:27:49.793  E/Test: IdleHandler2 queueIdle

可以看到,当 queueIdle() 方法返回 true时会多次执行,即 IdleHandler 执行一次后不会从 IdleHandler 的队列中删除,等下次空闲时间到来时还会继续执行。

 

 

   Message next() {
      ...

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

           ...// Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

 

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

比如在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中有使用到,具体可以自行去查看

posted on 2019-12-24 15:44  mingfeng002  阅读(4097)  评论(1编辑  收藏  举报