深入浅出Handler(七) IdleHandler的巧用

1,017 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

一、前言

之前的同步屏障我们提到了如何提高消息队列中消息的优先级,那有些消息可能就比较懂事了。

他们知道轮询的消息机一直很忙,又提出了一个需求:大哥,我知道你很忙,你先处理你要干的事,我这个活吧,优先级别不是特别高,能不能不忙的时候帮我干一下我的活?

二、IdleHandler

其实Android内部已经提供了一个IdleHandler的接口,帮我们去做这个逻辑判断了.

在MessageQueue中可以看到这么一个接口

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.
     */
    //当MessageQueue中没有更多的消息的时候就会回调queueIdle()这个方法
    //如果返回true,当MessageQueue中没有消息时还会继续回调这个方法
    //返回false则会在执行完后移除这个监听
    boolean queueIdle();
}

2.1 添加和移除

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

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

//移除
public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

可以看到IdleHandler被一个list的mIdleHandlers管理

2.2 取出消息

消息依然是在messageQueue.next()方法中

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

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

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            //首次空闲时赋值
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //如果没有IdleHandler直接continue
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            
            //mPendingIdleHandlers创建一个IdleHandler数组用于存放
            if (mPendingIdleHandlers == null) {
            //这里数组的最小长度为4,猜想是为了性能考虑,减少重复创建数组的几率
            //因为下边的toArray方法内部会判断,如果list长度小于传参的数组长度,则会直接拷贝进这个数组,否则会创建一个新的数组
                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 {
                //获取idler.queueIdle的返回值
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    //如果返回了false则移除
                    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;
    }
}
  • 可以看到,当MessageQueue为空,没有消息或者MessageQueue中最近需要处理的消息是延迟消息时,此时都会尝试执行IdleHandler.

  • 这次next()方法中 pendingIdleHandlerCount赋值为-1,后面会将mIdleHandlers.size()赋值给pendingIdleHandlerCount

  • 将mIdleHandlers中的IdleHandler拷贝到临时数组中

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

  • 当keep为false时,从mIdleHandler中移除当前循环的IdleHandler,否则继续保留

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ...

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found
             ....
             //没有需要空闲处理的消息,nextPollTimeoutMillis没有被重置
            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);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

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

       //走到这里说明肯定执行过 空闲任务了,此时可能会有任务进来,需要重置一下阻塞等待时间
        nextPollTimeoutMillis = 0;
    }
}

2.3 使用场景

当我们希望能够在当前线程消息队列,空闲时做些事情的时候可以使用.

比如咱们的gc垃圾回收机制

image.png

ActvityThread

void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        //添加GC任务
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        purgePendingResources();
        //这里返回的是false
        return false;
    }
}

这个GcIdler的scheduleGcIdler();何时被调用呢?

收到GC_WHEN_IDLE消息时

当AMS中的这两个方法被调用时,GC_WHEN_IDLE消息会被收到

  • 1、doLowMemReportIfNeededLocked--内存不够时
  • 2、activityIdle,ActivityThread的handleResumeActivity方法被调用时

还有一些三方库比如LeakCanary也是使用了IdleHandler判断内存泄露的,后面会陆续分析~

或者让开发者头疼的App启动优化,我们有些优先级别较低的缓存加载策略,就可以使IdleHandler