背景:
經常在做一些app冷啟動速度優化等性能優化工作時候,經常可能會發現有時候需要引入一些第三方sdk,或者庫,這些庫一般會要求我們在onCreate中進行初始化等,但是onCreate屬于生命周期的回調方法,如果onCreate中業務過多就會影響整個app的冷啟動時長,很多人這個時候第一想到可能是放入子線程等,但是經常又發現有的調用還要求在主線程調用等限制,所以針對這種情況下就經常會使用到一個IdleHandler,這個當初使用時候大家可能就只是字面意思理解為空閑的Handler,在空閑時候執行,其實沒有深入去看看它到底實現原理是什么,今天剛好有機會就來給大家進行一個剖析。
IdleHandler 的概念
IdleHandler 是 MessageQueue 提供的一個接口,用于監聽消息隊列的空閑狀態。它允許開發者在主線程處于空閑時,執行一些低優先級的任務。IdleHandler 的核心方法 queueIdle() 會在系統檢測到消息隊列空閑時調用。返回值決定了 IdleHandler 是否繼續在隊列中保留:若返回 true,則該回調在下次隊列空閑時繼續執行;反之,返回 false 則表示僅執行一次后移除。
IdleHandler 和性能優化
IdleHandler 的出現正好迎合了性能優化的需求。在實際開發中,有一些任務并非必須實時完成,例如日志上報、資源預加載、緩存清理、數據統計、一些動畫或預渲染任務等。利用 IdleHandler,可以將這些不緊急的任務推遲到主線程消息隊列空閑時再執行,從而避免干擾用戶看到的實時界面更新,提高整體界面流暢度和響應速度。因此,在 UI 主線程相對繁忙時,通過 IdleHandler 來分攤任務,可以讓系統先處理用戶的核心交互,就比如onCreate是生命周期方法,如理里面初始化太多東西影響冷啟動速度,針對一些可以延后不那么緊急任務可以待系統空閑時再處理任務,充分利用 CPU 空閑時間。
源碼分析IdleHandler
為了更加全面了解IdleHandler,下面將要從以下幾個部分進行IdleHandler的源碼分析。
1、
IdleHandler的接口解釋:
frameworks/base/core/java/android/os/MessageQueue.java
/*** 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就是對應的回調函數,這個queueIdle有一個返回值true或false,這兩個值分別有如下區別:
如果返回true意味著下一次空閑依舊會執行這個IdleHandler的回調函數。
如果返回false就代表當前IdleHandler已經執行完畢,下次空閑不會再執行該IdleHandler。
添加IdleHandler接口
/*** Add a new {@link IdleHandler} to this message queue. This may be* removed automatically for you by returning false from* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is* invoked, or explicitly removing it with {@link #removeIdleHandler}.** <p>This method is safe to call from any thread.** @param handler The IdleHandler to be added.*/public void addIdleHandler(@NonNull IdleHandler handler) {if (handler == null) {throw new NullPointerException("Can't add a null IdleHandler");}synchronized (this) {//這里其實就是加入到mIdleHandlers這個集合中mIdleHandlers.add(handler);}}
這里的addIdleHandler添加就是簡單加入的mIdleHandlers這個集合中,方便后面執行時候進行遍歷
IdleHandler的觸發執行
@UnsupportedAppUsageMessage next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {//進入等待消息,要被喚醒2種情況,1被其他函數wake,2等待時間nextPollTimeoutMillis到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;//當消息的Handler為空時,則查詢異步消息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) {//檢測msg時間是還沒到了if (now < msg.when) {// Next message is not ready. Set a timeout to wake up when it is ready.//還沒到msg的處理時間,那么就會與當前時間進行差值,這個差值就是等待時間nextPollTimeoutMillisnextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {//設置下一個消息為mMessagesmMessages = msg.next;}msg.next = null;msg.markInUse();//直接return回去消息,也就是有消息都在這里return了不會執行下面業務return msg;}} else {//沒有找到消息// No more messages.nextPollTimeoutMillis = -1;}// 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.//找了一圈發現沒有要處理的消息了,那么就開始判斷是否有IdleHandler的消息要處理//注意這里的pendingIdleHandlerCount第一次進入才是-1,后續一旦執行IdleHandler都是變成0,不會再進入if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {//把mIdleHandlers集合大小賦值給pendingIdleHandlerCountpendingIdleHandlerCount = mIdleHandlers.size();}//如果發現沒有IdleHandler,那么就返回循環進行block阻塞等待喚醒if (pendingIdleHandlerCount <= 0) {// No idle handlers to run. Loop and wait some more.mBlocked = true;continue;}//如果mPendingIdleHandlers這個數組沒有初始化就進行初始化,這里最小大小為4應該是為了性能以防頻繁擴容if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}//mIdleHandlers轉成了數組mPendingIdleHandlersmPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.//開始正式執行對應的IdleHandlerfor (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {//執行IdleHandler的對應回調業務方法keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}//這里的keep就是queueIdle的返回值,如果返回false就會從mIdleHandlers進行移除if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}//把pendingIdleHandlerCount變成0,不是-1,這樣防止死循環執行IdleHandler// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;//這里是為了讓盡快遍歷一下是否有消息,因為可能執行IdleHandler期間有新消息// 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;}}
上面next方法,關于IdleHandler執行總結如下:
1、next方法遍歷消息隊列,確實沒有尋找到要處理的消息任務
2、沒有要處理的消息則開始處理IdleHandler相關的任務
3、如果IdleHandler的queueIdle返回false則會從mIdleHandlers刪除,下次空閑就不會在執行這個IdleHandler,否則true的話會每次空閑都執行
實戰案例
測試一下queueIdle返回false,即只執行一次queueIdle
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 執行一些不那么緊急任務 Trace.beginSection("queueIdle no next runing");Log.i("lsm66666","queueIdle return false,no next runing");Trace.endSection();return false;}});}
驗證結果:
再測試一下queueIdle返回true,即執行多次queueIdle
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 執行一些不那么緊急任務 Trace.beginSection("queueIdle has next runing");Log.i("lsm66666","queueIdle return true,has next runing");Trace.endSection();return true;}});}
看看日志打印情況
systrace結合分析IdleHandler
上面也可以看出來IdleHandler的執行都在沒有消息可以處理了,才會執行,而且執行完成后就進入消息等待時期。
從上面也可以看出來,IdleHandler確實是在主線程空閑暫時沒有消息處理時候進行執行的,可以起到一個錯峰執行的目的,不過也要注意以下幾點:
1、切勿讓IdleHandler執行耗時操作,應該是可以快速完成的一些任務
2、盡量都讓IdleHandler執行是一次性任務,即queueIdle返回false,以防每次空閑都有重復調用
文章參考:https://mp.weixin.qq.com/s/7R2ws-6oBij3OrNMA_IfTA
更多framework實戰開發干貨,請關注下面“千里馬學框架”