Android: IdleHandler の簡単な理解と使用

1.IdleHandlerとは何ですか

率直に言うと、IdleHandler は Handler メカニズムによって提供されるメカニズムであり、Looper イベント ループ中にアイドル状態のときにタスクを実行できるようにします。

IdleHandlerはインターフェースであるMessageQueueに定義されています。

public final class MessageQueue {
    
    
    public static interface IdleHandler {
    
    
        boolean queueIdle();
    }
}
  • 戻り値は false、つまり 1 回だけ実行されます。
  • 戻り値は true です。つまり、メッセージ キュー内にすぐに実行する必要があるメッセージがなくなるたびに、このメソッドがトリガーされます。

基本的な使い方

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

2.IdleHandlerの使い方

2.1. 追加と削除

メッセージキュー内:

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

グローバル変数でmIdleHandlersすべてを管理IdleHandler

2.2. 実行

IdleHandler は主に MessageQueue がアイドル状態のときに実行されるため、いつアイドル状態になるのでしょうか。

MessageQueue は消息触发时间優先度ベースのリンク リストであるため、アイドル状態には 2 つのシナリオがあります。

  • MessageQueue が空です。没有消息;
  • MessageQueue で最近処理する必要があるメッセージは延迟消息(でありwhen>currentTime)、遅延して実行する必要があります。

どちらのシナリオでも IdleHandler の実行が試行されます。

IdleHandler のシーンに対処するために、メッセージ キュー内で次に実行されるメッセージを取得するMessage.next()この、具体的なロジックに従ってみましょう。

    /** 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. メッセージがこのサイクルで取得された場合Message为空、またはメッセージが延时的消息( ) で指定されたトリガー時刻がまだ到着していない場合、現在のキューは(この時点では)now < mMessages.whenと判断されます。空闲状态nextPollTimeoutMillis は -1)。

2.mPendingIdleHandlers配列を走査して (この配列内の要素はmIdleHandlers毎回フェッチされます)、各IdleHandlerインスタンスのメソッドを呼び出しますqueueIdle

  • このpendingIdleHandlerCount < 0とき、mIdleHandlers.size()への割り当てに従って、
    pendingIdleHandlerCount後のサイクルの基礎となります。
  • mIdleHandlersinIdleHandlermPendingIdleHandlersarrayコピーします。この配列は一時的なものであり、for ループに入ります。
  • ループ内で配列からそれを取り出しIdleHandler、呼び出して戻り値queueIdle()を記録し、keepに格納します。

3. このメソッドが を返した場合false、このインスタンスは、mIdleHandlers 中移除次回キューがアイドル状態になったときに、引き続き queueIdle メソッドをコールバックしません。

4. 処理IdleHandler後はnextPollTimeoutMillis0 に設定されます。ノンブロッキングメッセージキューもちろん、ここで実行されるコードは同期的に実行されるため、あまり時間がかかりすぎると、後続のメッセージの実行に確実に影響を与えることに注意してください。

ここに画像の説明を挿入

3. 一般的な問題と使用シナリオ

3.1. 使用シナリオ

IdleHandler の特性に従って、その使用シナリオは次の基本原則に従います在不影响其他任务,在消息队列空闲状态下执行

例: Android フレームワーク レイヤーの GC シーンはこのメカニズムを使用しており、CPU がアイドル状態の場合にのみ GC に移行します。

メッセージApplicationThreadを受信すると、メソッドがトリガーされます。GC_WHEN_IDLEscheduleGcIdler

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

ActivityThreadメソッドscheduleGcIdler(ApplicationThreadこれはActivityThread非静的内部クラスであるため、対応するメソッドを直接呼び出すことができます):

// 添加垃圾回收的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");
    }
}

ここでは、カスタムIdleHandlerGC 呼び出しが行われ、线程空闲時間が来たら呼び出されmGcIdler、最後にBinderInternal.forceGc("bg")メソッドによって GC がトリガーされます。この GC の最小間隔は 5 秒 (MIN_TIME_BETWEEN_GCS値) です。そして、返されるmGcIdlerものはqueueIdleであるfalseため、これはmGcIdlerメインスレッドに長期間存在することに注意してくださいMessageQueue

その他の一般的なシナリオ: IdleHandler の基本的な使用法とアプリケーションのケース分析

3.2. よくある質問

Q: IdleHandler は主に MessageQueue がアイドル状態のときに実行されますが、いつアイドル状態になりますか?

MessageQueue はメッセージ トリガー時間に基づく優先キューであるため、キューがアイドル状態になる場合は 2 つのシナリオがあります。

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

Q: IdleHandler の用途は何ですか?

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

Q: MessageQueue には IdleHandler の追加/削除メソッドが用意されていますが、これらはペアで使用する必要がありますか?

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

Q: mIdleHanders が空でない場合に無限ループに入らないのはなぜですか?

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

Q: 一部の重要でないスタートアップ サービスを IdleHandler に移動して処理できますか?

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

Q: IdleHandler の queueIdle() はどのスレッドで実行されますか?

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

参考

1.面接官: 「あなたの履歴書には、Handler メカニズムに精通していると書いてあります。それでは、IdleHandlerについて話しましょう?


おすすめ

転載: blog.csdn.net/JMW1407/article/details/129133416