持续创作,加速成长!这是我参加「日新计划 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垃圾回收机制

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

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