IdleHandler原理分析

1.使用方法及场景

之前做过冷启动优化,在冷启动的场景有很多的任务其实并不需要马上启动,通常的做法就是做一个延迟启动,如下所示

Handler mHandler = new Handler();
mHandler.postDelayed(() -> {
    //do something
}, 1000);

将任务延迟启动1000ms,但是这个延迟启动的时间不好确定,只能是自己预估的,对于一些高端手机1000ms可能多了,一些低端手机可能1000ms还不够。这个时候IdelHandler就可以解决这个问题,它能够在CPU空闲的时候再执行指定的任务。

使用方法也很简单,如下所示,调用addIdleHandler方法就可以了

MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        //do something
        return false;
    }
};
Looper.getMainLooper().getQueue().addIdleHandler(idleHandler);

2.基本原理

我们来看下IdelHandler到底是怎么实现的。
首先来看下这个接口,如注释所示,返回true和返回false是有区别的。

    public static interface IdleHandler {
        //返回true,表示这个接口一直有效,下次cpu空闲时还会调用
        //返回false,表示这个接口只会被调用一次
        boolean queueIdle();
    }

而addIdleHandler会将IdelHandler插入到mIdleHandlers里面,mIdleHandlers是一个ArrayList

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

接着我们来看下MessageQueue的next函数,这里是MessageQueue拿到下一条消息的逻辑。
next函数是一个死循环,在注释1处是一个阻塞函数,能够解除阻塞并拿到返回值只有三种情况,出错,超时,有消息产生。
注释2处省略了代码,就是从链表当中取出普通消息,如果有则直接返回。
从注释3处就开始处理IdelHandler,我们可以看到pendingIdleHandlerCount表示idelHandlers的个数,初始值为-1,当mMessage为空即当前无消息需要处理,或者当前的时间小于消息的时间即当前的消息还不需要处理,然后pendingIdleHandlerCount才真正获取idelHandlers的size。
注释4处表示,返回值为false时,就直接从列表当中移除,当下次cpu空闲时,就不再触发这个IdelHandler了。

Message next() {
    ......
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //1.阻塞函数
        nativePollOnce(ptr, nextPollTimeoutMillis);
        //2.从链表里面取出消息
        ......
            //3.开始处理IdelHandler
            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);
        }

        // 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);
            }
            //4.如果keep为false,则就从列表当中去除
            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;
    }
}

3.总结

其实IdelHandler的原理挺简单的,我们可以根据它的特性,需要懒加载的场景可以使用。
另外在framework当中有些场景也使用到了,如GC的场景,只有当cpu空闲的时候才会去gc

void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

你可能感兴趣的:(android)