Android: Simple understanding and use of IdleHandler

1. What is IdleHandler

To put it bluntly, IdleHandler is a mechanism provided by the Handler mechanism, which allows us to perform tasks when it is idle during the Looper event loop.

IdleHandler is defined in MessageQueue, which is an interface.

public final class MessageQueue {
    
    
    public static interface IdleHandler {
    
    
        boolean queueIdle();
    }
}
  • The return value is false, that is, it will only be executed once;
  • The return value is true, that is, this method will be triggered every time there is no message that needs to be executed immediately in the message queue.

Basic usage

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    
    
            @Override
            public boolean queueIdle() {
    
    
                Log.v(TAG, "#looper message has worked out");
                return false;
            }
        });
    }

2. How to use IdleHandler

2.1. Add and delete

In MessageQueue:

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

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);
    }
}

mIdleHandlersManage all through a global variableIdleHandler

2.2. Execution

Since IdleHandler is mainly executed when the MessageQueue is idle, when will it be idle?

MessageQueue is a 消息触发时间priority-based linked list, so there are two scenarios for idleness.

  • MessageQueue is empty, 没有消息;
  • The message that needs to be processed recently in MessageQueue is a 延迟消息( when>currentTime), which needs to be executed in a delayed manner;

Both scenarios will try to execute IdleHandler.

To deal with the scene of IdleHandler, in Message.next()this method of obtaining the next message to be executed in the message queue, let's follow the specific logic.

    /** MessageQueue.class */
    @UnsupportedAppUsage
    Message next() {
    
    
        // ...

        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;
                // 注释1 屏障消息处理,获取异步消息
                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());
                }
                // 注释2 获取到Message不为null,则说明存在需要处理的Message
                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.
                    // MessageQueue无消息,设置延迟为-1,nativePollOnce无限等待,直到有消息
                    nextPollTimeoutMillis = -1;
                }

                // 注释3 执行到此处,说明没有需要执行的Message(MessageQueue为空,或者存在延迟Message)
                // 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();
                }
                // 注释4 IdleHandler队列为空,不执行,进入下一个循环,此后不再会执行IdleHandler判断,除非下次进入next方法
                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)];
                }
                // 注释5 mIdleHandlers数组赋值给 mPendingIdleHandlers
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 注释6 执行IdleHandler队列中的空闲任务
            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 {
    
    
                    // 注释7 执行任务逻辑,并返回任务释放移除
                    keep = idler.queueIdle();
                } catch (Throwable t) {
    
    
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
    
    
                    synchronized (this) {
    
    
                        // 注释8 移除任务,下次不再执行
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 注释9 重置IdleHandler的Count为0,避免下次重复执行IdleHandler队列
            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;
        }
    }

1. If the message is obtained in this cycle Message为空, or the message is a 延时的消息( now < mMessages.when) and the specified trigger time has not yet arrived, then the current queue is determined to be 空闲状态(at this timenextPollTimeoutMillis is -1)。

2. Traverse mPendingIdleHandlersthe array (the elements in this array will be mIdleHandlersfetched every time) to call the method of each IdleHandlerinstance queueIdle.

  • At that pendingIdleHandlerCount < 0time , it is the basis of the later cycle according to mIdleHandlers.size()the assignment to
    pendingIdleHandlerCount
  • mIdleHandlersCopy the in IdleHandlerto mPendingIdleHandlersthe array , this array is temporary, and then enter the for loop;
  • Take it out from the array in the loop IdleHandler, and call it queueIdle()to record the return value and store it keepin ;

3. If this method returns false, then this instance mIdleHandlers 中移除will not continue to call back its queueIdle method when the queue is idle next time.

4. IdleHandlerAfter will nextPollTimeoutMillisbe set to 0, that isnon-blocking message queueOf course, it should be noted that the code executed here should not be too time-consuming, because it is executed synchronously. If it is too time-consuming, it will definitely affect the subsequent message execution.

insert image description here

3. Common problems and usage scenarios

3.1. Usage scenarios

According to the characteristics of IdleHandler, its usage scenarios follow a basic principle: 在不影响其他任务,在消息队列空闲状态下执行.

For example: the GC scene of the Android Framework layer uses this mechanism, and only when the CPU is idle will it go to the GC:

After ApplicationThreadreceiving GC_WHEN_IDLEthe message, scheduleGcIdlerthe method will be triggered:

class H extends Handler {
    
    
    ......
    public static final int GC_WHEN_IDLE = 120;
    ......
    
    public void handleMessage(Message msg) {
    
    
        switch (msg.what) {
    
    
            ......
            case GC_WHEN_IDLE:
                scheduleGcIdler();
            ......
        }
    }
}

In ActivityThreadthe scheduleGcIdlermethod ( ApplicationThreadit is ActivityThreada non-static inner class, so the corresponding method can be called directly):

// 添加垃圾回收的IdleHandler
void scheduleGcIdler() {
    
    
    if (!mGcIdlerScheduled) {
    
    
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

// 移除垃圾回收的IdleHandler
void unscheduleGcIdler() {
    
    
    if (mGcIdlerScheduled) {
    
    
        mGcIdlerScheduled = false;
        Looper.myQueue().removeIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

final GcIdler mGcIdler = new GcIdler();

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

void doGcIfNeeded() {
    
    
    mGcIdlerScheduled = false;
    final long now = SystemClock.uptimeMillis();
    if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
    
    
        // 执行垃圾回收
        BinderInternal.forceGc("bg");
    }
}

Here, a custom IdleHandlerGC call is made, 线程空闲which will be called when the time comes mGcIdler, and finally BinderInternal.forceGc("bg")the GC is triggered by the method. The minimum interval of this GC is 5 seconds ( MIN_TIME_BETWEEN_GCSthe value). And pay attention, mGcIdlerwhat is queueIdlereturned is false, so this mGcIdlerwill exist in the main thread for a long time MessageQueue.

Other common scenarios: IdleHandler basic usage and application case analysis

3.2. Frequently asked questions

Q: IdleHandler is mainly executed when the MessageQueue is idle, so when will it be idle?

MessageQueue is a priority queue based on message trigger time, so there are two scenarios when the queue becomes idle.

MessageQueue 为空,没有消息;
MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;

Q: What is the use of IdleHandler?

IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;

Q: MessageQueue provides add/remove IdleHandler methods, do they need to be used in pairs?

不是必须;
IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;

Q: Why doesn't it enter an infinite loop when mIdleHanders is not empty?

只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

Q: Can some unimportant startup services be moved to IdleHandler for processing?

不建议;
IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;

Q: Which thread does queueIdle() of IdleHandler run on?

陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;

reference

1. Interviewer: "It is written on your resume that you are familiar with the Handler mechanism, so let's talk about IdleHandler ?
" ) synchronization barrier with IdleHandler

Guess you like

Origin blog.csdn.net/JMW1407/article/details/129133416