Android IdleHandler 原理淺析


IdleHandler:空閑監聽器(就像我沒事做了,在群里發了個表情,這時候其他人就知道我很閑了)

在每次next獲取消息進行處理時,發現沒有可以處理的消息(隊列空,只有延時消息並且沒到時間,同步阻塞時沒有異步消息)都會通知這些訂閱者。

適合做一些可有可無的東西,因為這個通知太不穩定了(就像別人說過幾天請你吃飯,你要真傻等着你估計能餓死)。

1.怎么使用?

要使用IdleHandler只需要調用MessageQueue#addIdleHandler(IdleHandler handler)方法即

Looper.myQueue().addIdleHandler(new IdleHandler() {

    @Override
    public boolean queueIdle() {
        ...
        return false;
    }
}

 2.源碼分析

首先看下源碼的解釋

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    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.
         */

        boolean queueIdle();

    }

注釋中很明確地指出當消息隊列空閑時會執行IdleHandler的queueIdle()方法,該方法返回一個boolean值,

如果為false則執行完畢之后移除這條消息(一次性完事),

如果為true則保留,等到下次空閑時會再次執行(重復執行)。

 

看MessageQueue的源碼可以發現有兩處關於IdleHandler的聲明,分明是:

  • 存放IdleHandler的ArrayList(mIdleHandlers),
  • 還有一個IdleHandler數組(mPendingIdleHandlers)。

后面的數組,它里面放的IdleHandler實例都是臨時的,也就是每次使用完(調用了queueIdle方法)之后,都會置空(mPendingIdleHandlers[i] = null)

下面看下MessageQueue的next()方法可以發現確實是這樣

Message next() {
        ......
        for (;;) {
            ......
            synchronized (this) {
        // 此處為正常消息隊列的處理
                ......
                if (mQuitting) {
                    dispose();
                    return null;
                }
                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);
            }

            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);
                    }
                }
            }
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

就在MessageQueue的next方法里面。

大概流程是這樣的:

  1. 如果本次循環拿到的Message為空,或者!這個Message是一個延時的消息而且還沒到指定的觸發時間,那么,就認定當前的隊列為空閑狀態,
  2. 接着就會遍歷mPendingIdleHandlers數組(這個數組里面的元素每次都會到mIdleHandlers中去拿)來調用每一個IdleHandler實例的queueIdle方法,
  3. 如果這個方法返回false的話,那么這個實例就會從mIdleHandlers中移除,也就是當下次隊列空閑的時候,不會繼續回調它的queueIdle方法了。

處理完IdleHandler后會將nextPollTimeoutMillis設置為0,也就是不阻塞消息隊列,當然要注意這里執行的代碼同樣不能太耗時,因為它是同步執行的,如果太耗時肯定會影響后面的message執行。

能力大概就是上面講的那樣,那么能力決定用處,用處從本質上講就是趁着消息隊列空閑的時候干點事情,當然具體的用處還是要看具體的處理。

3.IdleHandler返回true和false帶來的不同結果

queueIdle() 方法如果返回 false,那么這個 IdleHandler 只會執行一次,執行完后會從隊列中刪除,如果返回 true,那么執行完后不會被刪除,只要執行 MessageQueue.next 時消息隊列中沒有可執行的消息,即為空閑時間,那么 IdleHandler 隊列中的 IdleHandler 還會繼續被執行。

public void clickTestIdleHandler(View view) {
    // 添加第一個 IdleHandler
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","IdleHandler1 queueIdle");
            return false;
        }
    });
    // 添加第二個 IdleHandler
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","IdleHandler2 queueIdle");
            return false;
        }
    });
    Handler handler = new Handler();
    // 添加第一個Message
    Message message1 = Message.obtain(handler, new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","message1");
        }
    });
    message1.sendToTarget();
    // 添加第二個Message
    Message message2 = Message.obtain(handler, new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e("Test","message2");
        }
    });
    message2.sendToTarget();
}

上面的例子中分別向消息隊列中添加來兩個 IdleHandler 和兩個 Message,其中 IdleHandler 的 queueIdle() 方法返回 false,下面來看一下結果:

16:23:07.726 E/Test: message1
16:23:09.727 E/Test: message2
16:23:11.732 E/Test: IdleHandler1 queueIdle
16:23:13.733 E/Test: IdleHandler2 queueIdle

可以看到 IdleHandler 是在消息隊列的其它消息執行完后才執行的,而且只執行來一次。
再來看一下 queueIdle() 方法返回 true 的情況:

16:27:02.083  E/Test: message1
16:27:04.084  E/Test: message2
16:27:06.090  E/Test: IdleHandler1 queueIdle
16:27:08.090  E/Test: IdleHandler2 queueIdle
16:27:10.095  E/Test: IdleHandler1 queueIdle
16:27:12.096  E/Test: IdleHandler2 queueIdle
16:27:14.099  E/Test: IdleHandler1 queueIdle
16:27:16.099  E/Test: IdleHandler2 queueIdle
16:27:43.788  E/Test: IdleHandler1 queueIdle
16:27:45.788  E/Test: IdleHandler2 queueIdle
16:27:47.792  E/Test: IdleHandler1 queueIdle
16:27:49.793  E/Test: IdleHandler2 queueIdle

可以看到,當 queueIdle() 方法返回 true時會多次執行,即 IdleHandler 執行一次后不會從 IdleHandler 的隊列中刪除,等下次空閑時間到來時還會繼續執行。

 

4.來看看它在源碼里的使用場景:

比如在ActivityThread中,就有一個名叫GcIdler的內部類,實現了IdleHandler接口。

它在queueIdle方法被回調時,會做強行GC的操作(即調用BinderInternal的forceGc方法),但強行GC的前提是,與上一次強行GC至少相隔5秒以上。 

ActivityThread中GcIdler

那么我們就看看最為明顯的ActivityThread中聲明的GcIdler。在ActivityThread中的 H收到 GC_WHEN_IDLE消息后,會執行scheduleGcIdler,將GcIdler添加到MessageQueue中的空閑任務集合中。具體如下:
 void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //添加GC任務
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

ActivityThread中GcIdler的詳細聲明:

   //GC任務
   final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            //執行后,就直接刪除
            return false;
        }
    }
    // 判斷是否需要執行垃圾回收。
    void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //獲取上次GC的時間
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }

GcIdler方法理解起來很簡單、就是獲取上次GC的時間,判斷是否需要GC操作。如果需要則進行GC操作。這里ActivityThread中還聲明了其他空閑時的任務。如果大家對其他空閑任務感興趣,可以自行研究。

那這個GcIdler會在什么時候使用呢?

當ActivityThread的mH(Handler)收到GC_WHEN_IDLE消息之后。

何時會收到GC_WHEN_IDLE消息?

當AMS(ActivityManagerService)中的這兩個方法被調用之后:

  • doLowMemReportIfNeededLocked, 這個方法看名字就知道是不夠內存的時候調用的了。
  • activityIdle, 這個方法呢,就是當ActivityThread的handleResumeActivity方法被調用時(Activity的onResume方法也是在這方法里回調)調用的。

5.其他可以想到的場景

1.Activity啟動優化:onCreate,onStart,onResume中耗時較短但非必要的代碼可以放到IdleHandler中執行,減少啟動時間

2.想要在一個View繪制完成之后添加其他依賴於這個View的View,當然這個用View#post()也能實現,區別就是前者會在消息隊列空閑時執行

3.發送一個返回true的IdleHandler,在里面讓某個View不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個View,這也是種很酷的操作

4.一些第三方庫中有使用,比如LeakCanary,Glide中有使用到,具體可以自行去查看


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM